diff --git a/common/djangoapps/request_cache/__init__.py b/common/djangoapps/request_cache/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/common/djangoapps/request_cache/middleware.py b/common/djangoapps/request_cache/middleware.py
new file mode 100644
index 0000000000..9d3dffdf27
--- /dev/null
+++ b/common/djangoapps/request_cache/middleware.py
@@ -0,0 +1,20 @@
+import threading
+
+_request_cache_threadlocal = threading.local()
+_request_cache_threadlocal.data = {}
+
+class RequestCache(object):
+ @classmethod
+ def get_request_cache(cls):
+ return _request_cache_threadlocal
+
+ def clear_request_cache(self):
+ _request_cache_threadlocal.data = {}
+
+ def process_request(self, request):
+ self.clear_request_cache()
+ return None
+
+ def process_response(self, request, response):
+ self.clear_request_cache()
+ return response
\ No newline at end of file
diff --git a/common/djangoapps/terrain/course_helpers.py b/common/djangoapps/terrain/course_helpers.py
new file mode 100644
index 0000000000..f0df456c80
--- /dev/null
+++ b/common/djangoapps/terrain/course_helpers.py
@@ -0,0 +1,140 @@
+#pylint: disable=C0111
+#pylint: disable=W0621
+
+from lettuce import world, step
+from .factories import *
+from django.conf import settings
+from django.http import HttpRequest
+from django.contrib.auth.models import User
+from django.contrib.auth import authenticate, login
+from django.contrib.auth.middleware import AuthenticationMiddleware
+from django.contrib.sessions.middleware import SessionMiddleware
+from student.models import CourseEnrollment
+from xmodule.modulestore.django import _MODULESTORES, modulestore
+from xmodule.templates import update_templates
+from bs4 import BeautifulSoup
+import os.path
+from urllib import quote_plus
+from lettuce.django import django_url
+
+
+@world.absorb
+def create_user(uname):
+
+ # If the user already exists, don't try to create it again
+ if len(User.objects.filter(username=uname)) > 0:
+ return
+
+ portal_user = UserFactory.build(username=uname, email=uname + '@edx.org')
+ portal_user.set_password('test')
+ portal_user.save()
+
+ registration = world.RegistrationFactory(user=portal_user)
+ registration.register(portal_user)
+ registration.activate()
+
+ user_profile = world.UserProfileFactory(user=portal_user)
+
+
+@world.absorb
+def log_in(username, password):
+ '''
+ Log the user in programatically
+ '''
+
+ # Authenticate the user
+ user = authenticate(username=username, password=password)
+ assert(user is not None and user.is_active)
+
+ # Send a fake HttpRequest to log the user in
+ # We need to process the request using
+ # Session middleware and Authentication middleware
+ # to ensure that session state can be stored
+ request = HttpRequest()
+ SessionMiddleware().process_request(request)
+ AuthenticationMiddleware().process_request(request)
+ login(request, user)
+
+ # Save the session
+ request.session.save()
+
+ # Retrieve the sessionid and add it to the browser's cookies
+ cookie_dict = {settings.SESSION_COOKIE_NAME: request.session.session_key}
+ try:
+ world.browser.cookies.add(cookie_dict)
+
+ # WebDriver has an issue where we cannot set cookies
+ # before we make a GET request, so if we get an error,
+ # we load the '/' page and try again
+ except:
+ world.browser.visit(django_url('/'))
+ world.browser.cookies.add(cookie_dict)
+
+
+@world.absorb
+def register_by_course_id(course_id, is_staff=False):
+ create_user('robot')
+ u = User.objects.get(username='robot')
+ if is_staff:
+ u.is_staff = True
+ u.save()
+ CourseEnrollment.objects.get_or_create(user=u, course_id=course_id)
+
+
+
+@world.absorb
+def save_the_course_content(path='/tmp'):
+ html = world.browser.html.encode('ascii', 'ignore')
+ soup = BeautifulSoup(html)
+
+ # get rid of the header, we only want to compare the body
+ soup.head.decompose()
+
+ # for now, remove the data-id attributes, because they are
+ # causing mismatches between cms-master and master
+ for item in soup.find_all(attrs={'data-id': re.compile('.*')}):
+ del item['data-id']
+
+ # we also need to remove them from unrendered problems,
+ # where they are contained in the text of divs instead of
+ # in attributes of tags
+ # Be careful of whether or not it was the last attribute
+ # and needs a trailing space
+ for item in soup.find_all(text=re.compile(' data-id=".*?" ')):
+ s = unicode(item.string)
+ item.string.replace_with(re.sub(' data-id=".*?" ', ' ', s))
+
+ for item in soup.find_all(text=re.compile(' data-id=".*?"')):
+ s = unicode(item.string)
+ item.string.replace_with(re.sub(' data-id=".*?"', ' ', s))
+
+ # prettify the html so it will compare better, with
+ # each HTML tag on its own line
+ output = soup.prettify()
+
+ # use string slicing to grab everything after 'courseware/' in the URL
+ u = world.browser.url
+ section_url = u[u.find('courseware/') + 11:]
+
+
+ if not os.path.exists(path):
+ os.makedirs(path)
+
+ filename = '%s.html' % (quote_plus(section_url))
+ f = open('%s/%s' % (path, filename), 'w')
+ f.write(output)
+ f.close
+
+
+@world.absorb
+def clear_courses():
+ # Flush and initialize the module store
+ # It needs the templates because it creates new records
+ # by cloning from the template.
+ # Note that if your test module gets in some weird state
+ # (though it shouldn't), do this manually
+ # from the bash shell to drop it:
+ # $ mongo test_xmodule --eval "db.dropDatabase()"
+ _MODULESTORES = {}
+ modulestore().collection.drop()
+ update_templates()
diff --git a/common/djangoapps/terrain/steps.py b/common/djangoapps/terrain/steps.py
index 3bc838a6af..a8a32db173 100644
--- a/common/djangoapps/terrain/steps.py
+++ b/common/djangoapps/terrain/steps.py
@@ -1,20 +1,12 @@
+#pylint: disable=C0111
+#pylint: disable=W0621
+
from lettuce import world, step
-from .factories import *
+from .course_helpers import *
+from .ui_helpers import *
from lettuce.django import django_url
-from django.conf import settings
-from django.http import HttpRequest
-from django.contrib.auth.models import User
-from django.contrib.auth import authenticate, login
-from django.contrib.auth.middleware import AuthenticationMiddleware
-from django.contrib.sessions.middleware import SessionMiddleware
-from student.models import CourseEnrollment
-from urllib import quote_plus
-from nose.tools import assert_equals
-from bs4 import BeautifulSoup
+from nose.tools import assert_equals, assert_in
import time
-import re
-import os.path
-from selenium.common.exceptions import WebDriverException
from logging import getLogger
logger = getLogger(__name__)
@@ -22,7 +14,7 @@ logger = getLogger(__name__)
@step(u'I wait (?:for )?"(\d+)" seconds?$')
def wait(step, seconds):
- time.sleep(float(seconds))
+ world.wait(seconds)
@step('I reload the page$')
@@ -37,42 +29,42 @@ def browser_back(step):
@step('I (?:visit|access|open) the homepage$')
def i_visit_the_homepage(step):
- world.browser.visit(django_url('/'))
- assert world.browser.is_element_present_by_css('header.global', 10)
+ world.visit('/')
+ assert world.is_css_present('header.global')
@step(u'I (?:visit|access|open) the dashboard$')
def i_visit_the_dashboard(step):
- world.browser.visit(django_url('/dashboard'))
- assert world.browser.is_element_present_by_css('section.container.dashboard', 5)
+ world.visit('/dashboard')
+ assert world.is_css_present('section.container.dashboard')
@step('I should be on the dashboard page$')
def i_should_be_on_the_dashboard(step):
- assert world.browser.is_element_present_by_css('section.container.dashboard', 5)
+ assert world.is_css_present('section.container.dashboard')
assert world.browser.title == 'Dashboard'
@step(u'I (?:visit|access|open) the courses page$')
def i_am_on_the_courses_page(step):
- world.browser.visit(django_url('/courses'))
- assert world.browser.is_element_present_by_css('section.courses')
+ world.visit('/courses')
+ assert world.is_css_present('section.courses')
@step(u'I press the "([^"]*)" button$')
def and_i_press_the_button(step, value):
button_css = 'input[value="%s"]' % value
- world.browser.find_by_css(button_css).first.click()
+ world.css_click(button_css)
@step(u'I click the link with the text "([^"]*)"$')
def click_the_link_with_the_text_group1(step, linktext):
- world.browser.find_link_by_text(linktext).first.click()
+ world.click_link(linktext)
@step('I should see that the path is "([^"]*)"$')
def i_should_see_that_the_path_is(step, path):
- assert world.browser.url == django_url(path)
+ assert world.url_equals(path)
@step(u'the page title should be "([^"]*)"$')
@@ -85,10 +77,15 @@ def the_page_title_should_contain(step, title):
assert(title in world.browser.title)
+@step('I log in$')
+def i_log_in(step):
+ world.log_in('robot', 'test')
+
+
@step('I am a logged in user$')
def i_am_logged_in_user(step):
- create_user('robot')
- log_in('robot', 'test')
+ world.create_user('robot')
+ world.log_in('robot', 'test')
@step('I am not logged in$')
@@ -98,151 +95,46 @@ def i_am_not_logged_in(step):
@step('I am staff for course "([^"]*)"$')
def i_am_staff_for_course_by_id(step, course_id):
- register_by_course_id(course_id, True)
+ world.register_by_course_id(course_id, True)
-@step('I log in$')
-def i_log_in(step):
- log_in('robot', 'test')
+@step(r'click (?:the|a) link (?:called|with the text) "([^"]*)"$')
+def click_the_link_called(step, text):
+ world.click_link(text)
+
+
+@step(r'should see that the url is "([^"]*)"$')
+def should_have_the_url(step, url):
+ assert_equals(world.browser.url, url)
+
+
+@step(r'should see (?:the|a) link (?:called|with the text) "([^"]*)"$')
+def should_see_a_link_called(step, text):
+ assert len(world.browser.find_link_by_text(text)) > 0
+
+
+@step(r'should see "(.*)" (?:somewhere|anywhere) in (?:the|this) page')
+def should_see_in_the_page(step, text):
+ assert_in(text, world.css_text('body'))
+
+
+@step('I am logged in$')
+def i_am_logged_in(step):
+ world.create_user('robot')
+ world.log_in('robot', 'test')
+ world.browser.visit(django_url('/'))
+
+
+@step('I am not logged in$')
+def i_am_not_logged_in(step):
+ world.browser.cookies.delete()
@step(u'I am an edX user$')
def i_am_an_edx_user(step):
- create_user('robot')
-
-#### helper functions
+ world.create_user('robot')
-@world.absorb
-def scroll_to_bottom():
- # Maximize the browser
- world.browser.execute_script("window.scrollTo(0, screen.height);")
-
-
-@world.absorb
-def create_user(uname):
-
- # If the user already exists, don't try to create it again
- if len(User.objects.filter(username=uname)) > 0:
- return
-
- portal_user = UserFactory.build(username=uname, email=uname + '@edx.org')
- portal_user.set_password('test')
- portal_user.save()
-
- registration = world.RegistrationFactory(user=portal_user)
- registration.register(portal_user)
- registration.activate()
-
- user_profile = world.UserProfileFactory(user=portal_user)
-
-
-@world.absorb
-def log_in(username, password):
- '''
- Log the user in programatically
- '''
-
- # Authenticate the user
- user = authenticate(username=username, password=password)
- assert(user is not None and user.is_active)
-
- # Send a fake HttpRequest to log the user in
- # We need to process the request using
- # Session middleware and Authentication middleware
- # to ensure that session state can be stored
- request = HttpRequest()
- SessionMiddleware().process_request(request)
- AuthenticationMiddleware().process_request(request)
- login(request, user)
-
- # Save the session
- request.session.save()
-
- # Retrieve the sessionid and add it to the browser's cookies
- cookie_dict = {settings.SESSION_COOKIE_NAME: request.session.session_key}
- try:
- world.browser.cookies.add(cookie_dict)
-
- # WebDriver has an issue where we cannot set cookies
- # before we make a GET request, so if we get an error,
- # we load the '/' page and try again
- except:
- world.browser.visit(django_url('/'))
- world.browser.cookies.add(cookie_dict)
-
-
-@world.absorb
-def register_by_course_id(course_id, is_staff=False):
- create_user('robot')
- u = User.objects.get(username='robot')
- if is_staff:
- u.is_staff = True
- u.save()
- CourseEnrollment.objects.get_or_create(user=u, course_id=course_id)
-
-
-@world.absorb
-def save_the_html(path='/tmp'):
- u = world.browser.url
- html = world.browser.html.encode('ascii', 'ignore')
- filename = '%s.html' % quote_plus(u)
- f = open('%s/%s' % (path, filename), 'w')
- f.write(html)
- f.close
-
-
-@world.absorb
-def save_the_course_content(path='/tmp'):
- html = world.browser.html.encode('ascii', 'ignore')
- soup = BeautifulSoup(html)
-
- # get rid of the header, we only want to compare the body
- soup.head.decompose()
-
- # for now, remove the data-id attributes, because they are
- # causing mismatches between cms-master and master
- for item in soup.find_all(attrs={'data-id': re.compile('.*')}):
- del item['data-id']
-
- # we also need to remove them from unrendered problems,
- # where they are contained in the text of divs instead of
- # in attributes of tags
- # Be careful of whether or not it was the last attribute
- # and needs a trailing space
- for item in soup.find_all(text=re.compile(' data-id=".*?" ')):
- s = unicode(item.string)
- item.string.replace_with(re.sub(' data-id=".*?" ', ' ', s))
-
- for item in soup.find_all(text=re.compile(' data-id=".*?"')):
- s = unicode(item.string)
- item.string.replace_with(re.sub(' data-id=".*?"', ' ', s))
-
- # prettify the html so it will compare better, with
- # each HTML tag on its own line
- output = soup.prettify()
-
- # use string slicing to grab everything after 'courseware/' in the URL
- u = world.browser.url
- section_url = u[u.find('courseware/') + 11:]
-
-
- if not os.path.exists(path):
- os.makedirs(path)
-
- filename = '%s.html' % (quote_plus(section_url))
- f = open('%s/%s' % (path, filename), 'w')
- f.write(output)
- f.close
-
-@world.absorb
-def css_click(css_selector):
- try:
- world.browser.find_by_css(css_selector).click()
-
- except WebDriverException:
- # Occassionally, MathJax or other JavaScript can cover up
- # an element temporarily.
- # If this happens, wait a second, then try again
- time.sleep(1)
- world.browser.find_by_css(css_selector).click()
+@step(u'User "([^"]*)" is an edX user$')
+def registered_edx_user(step, uname):
+ world.create_user(uname)
diff --git a/common/djangoapps/terrain/ui_helpers.py b/common/djangoapps/terrain/ui_helpers.py
new file mode 100644
index 0000000000..d4d99e17b5
--- /dev/null
+++ b/common/djangoapps/terrain/ui_helpers.py
@@ -0,0 +1,117 @@
+#pylint: disable=C0111
+#pylint: disable=W0621
+
+from lettuce import world, step
+import time
+from urllib import quote_plus
+from selenium.common.exceptions import WebDriverException
+from selenium.webdriver.support import expected_conditions as EC
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+from lettuce.django import django_url
+
+
+@world.absorb
+def wait(seconds):
+ time.sleep(float(seconds))
+
+
+@world.absorb
+def wait_for(func):
+ WebDriverWait(world.browser.driver, 5).until(func)
+
+
+@world.absorb
+def visit(url):
+ world.browser.visit(django_url(url))
+
+
+@world.absorb
+def url_equals(url):
+ return world.browser.url == django_url(url)
+
+
+@world.absorb
+def is_css_present(css_selector):
+ return world.browser.is_element_present_by_css(css_selector, wait_time=4)
+
+
+@world.absorb
+def css_has_text(css_selector, text):
+ return world.css_text(css_selector) == text
+
+
+@world.absorb
+def css_find(css):
+ def is_visible(driver):
+ return EC.visibility_of_element_located((By.CSS_SELECTOR, css,))
+
+ world.browser.is_element_present_by_css(css, 5)
+ wait_for(is_visible)
+ return world.browser.find_by_css(css)
+
+
+@world.absorb
+def css_click(css_selector):
+ '''
+ First try to use the regular click method,
+ but if clicking in the middle of an element
+ doesn't work it might be that it thinks some other
+ element is on top of it there so click in the upper left
+ '''
+ try:
+ world.browser.find_by_css(css_selector).click()
+
+ except WebDriverException:
+ # Occassionally, MathJax or other JavaScript can cover up
+ # an element temporarily.
+ # If this happens, wait a second, then try again
+ time.sleep(1)
+ world.browser.find_by_css(css_selector).click()
+
+
+@world.absorb
+def css_click_at(css, x=10, y=10):
+ '''
+ A method to click at x,y coordinates of the element
+ rather than in the center of the element
+ '''
+ e = css_find(css).first
+ e.action_chains.move_to_element_with_offset(e._element, x, y)
+ e.action_chains.click()
+ e.action_chains.perform()
+
+
+@world.absorb
+def css_fill(css_selector, text):
+ world.browser.find_by_css(css_selector).first.fill(text)
+
+
+@world.absorb
+def click_link(partial_text):
+ world.browser.find_link_by_partial_text(partial_text).first.click()
+
+
+@world.absorb
+def css_text(css_selector):
+
+ # Wait for the css selector to appear
+ if world.is_css_present(css_selector):
+ return world.browser.find_by_css(css_selector).first.text
+ else:
+ return ""
+
+
+@world.absorb
+def css_visible(css_selector):
+ return world.browser.find_by_css(css_selector).visible
+
+
+@world.absorb
+def save_the_html(path='/tmp'):
+ u = world.browser.url
+ html = world.browser.html.encode('ascii', 'ignore')
+ filename = '%s.html' % quote_plus(u)
+ f = open('%s/%s' % (path, filename), 'w')
+ f.write(html)
+ f.close
diff --git a/common/lib/xmodule/xmodule/modulestore/mongo.py b/common/lib/xmodule/xmodule/modulestore/mongo.py
index 38b15ab76e..47e35cda93 100644
--- a/common/lib/xmodule/xmodule/modulestore/mongo.py
+++ b/common/lib/xmodule/xmodule/modulestore/mongo.py
@@ -109,7 +109,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
references to metadata_inheritance_tree
"""
def __init__(self, modulestore, module_data, default_class, resources_fs,
- error_tracker, render_template, metadata_cache=None):
+ error_tracker, render_template, cached_metadata=None):
"""
modulestore: the module store that can be used to retrieve additional modules
@@ -134,7 +134,8 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
# cdodge: other Systems have a course_id attribute defined. To keep things consistent, let's
# define an attribute here as well, even though it's None
self.course_id = None
- self.metadata_cache = metadata_cache
+ self.cached_metadata = cached_metadata
+
def load_item(self, location):
"""
@@ -170,8 +171,8 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
model_data = DbModel(kvs, class_, None, MongoUsage(self.course_id, location))
module = class_(self, location, model_data)
- if self.metadata_cache is not None:
- metadata_to_inherit = self.metadata_cache.get(metadata_cache_key(location), {}).get('parent_metadata', {}).get(location.url(), {})
+ if self.cached_metadata is not None:
+ metadata_to_inherit = self.cached_metadata.get(location.url(), {})
inherit_metadata(module, metadata_to_inherit)
return module
except:
@@ -223,7 +224,8 @@ class MongoModuleStore(ModuleStoreBase):
def __init__(self, host, db, collection, fs_root, render_template,
port=27017, default_class=None,
error_tracker=null_error_tracker,
- user=None, password=None, **kwargs):
+ user=None, password=None, request_cache=None,
+ metadata_inheritance_cache_subsystem=None, **kwargs):
ModuleStoreBase.__init__(self)
@@ -254,8 +256,10 @@ class MongoModuleStore(ModuleStoreBase):
self.error_tracker = error_tracker
self.render_template = render_template
self.ignore_write_events_on_courses = []
+ self.request_cache = request_cache
+ self.metadata_inheritance_cache_subsystem = metadata_inheritance_cache_subsystem
- def get_metadata_inheritance_tree(self, location):
+ def compute_metadata_inheritance_tree(self, location):
'''
TODO (cdodge) This method can be deleted when the 'split module store' work has been completed
'''
@@ -323,32 +327,45 @@ class MongoModuleStore(ModuleStoreBase):
if root is not None:
_compute_inherited_metadata(root)
- return {'parent_metadata': metadata_to_inherit,
- 'timestamp': datetime.now()}
+ return metadata_to_inherit
- def get_cached_metadata_inheritance_trees(self, locations, force_refresh=False):
+ def get_cached_metadata_inheritance_tree(self, location, force_refresh=False):
'''
TODO (cdodge) This method can be deleted when the 'split module store' work has been completed
'''
+ key = metadata_cache_key(location)
+ tree = {}
+
+ if not force_refresh:
+ # see if we are first in the request cache (if present)
+ if self.request_cache is not None and key in self.request_cache.data.get('metadata_inheritance', {}):
+ return self.request_cache.data['metadata_inheritance'][key]
- trees = {}
- if locations and self.metadata_inheritance_cache is not None and not force_refresh:
- trees = self.metadata_inheritance_cache.get_many(list(set([metadata_cache_key(loc) for loc in locations])))
- else:
- # This is to help guard against an accident prod runtime without a cache
- logging.warning('Running MongoModuleStore without metadata_inheritance_cache. '
- 'This should not happen in production!')
+ # then look in any caching subsystem (e.g. memcached)
+ if self.metadata_inheritance_cache_subsystem is not None:
+ tree = self.metadata_inheritance_cache_subsystem.get(key, {})
+ else:
+ logging.warning('Running MongoModuleStore without a metadata_inheritance_cache_subsystem. This is OK in localdev and testing environment. Not OK in production.')
- to_cache = {}
- for loc in locations:
- cache_key = metadata_cache_key(loc)
- if cache_key not in trees:
- to_cache[cache_key] = trees[cache_key] = self.get_metadata_inheritance_tree(loc)
+ if not tree:
+ # if not in subsystem, or we are on force refresh, then we have to compute
+ tree = self.compute_metadata_inheritance_tree(location)
+
+ # now write out computed tree to caching subsystem (e.g. memcached), if available
+ if self.metadata_inheritance_cache_subsystem is not None:
+ self.metadata_inheritance_cache_subsystem.set(key, tree)
- if to_cache and self.metadata_inheritance_cache is not None:
- self.metadata_inheritance_cache.set_many(to_cache)
+ # now populate a request_cache, if available. NOTE, we are outside of the
+ # scope of the above if: statement so that after a memcache hit, it'll get
+ # put into the request_cache
+ if self.request_cache is not None:
+ # we can't assume the 'metadatat_inheritance' part of the request cache dict has been
+ # defined
+ if 'metadata_inheritance' not in self.request_cache.data:
+ self.request_cache.data['metadata_inheritance'] = {}
+ self.request_cache.data['metadata_inheritance'][key] = tree
- return trees
+ return tree
def refresh_cached_metadata_inheritance_tree(self, location):
"""
@@ -357,15 +374,7 @@ class MongoModuleStore(ModuleStoreBase):
"""
pseudo_course_id = '/'.join([location.org, location.course])
if pseudo_course_id not in self.ignore_write_events_on_courses:
- self.get_cached_metadata_inheritance_trees([location], force_refresh=True)
-
- def clear_cached_metadata_inheritance_tree(self, location):
- """
- Delete the cached metadata inheritance tree for the org/course combination
- for location
- """
- if self.metadata_inheritance_cache is not None:
- self.metadata_inheritance_cache.delete(metadata_cache_key(location))
+ self.get_cached_metadata_inheritance_tree(location, force_refresh=True)
def _clean_item_data(self, item):
"""
@@ -411,18 +420,7 @@ class MongoModuleStore(ModuleStoreBase):
return data
- def _cache_metadata_inheritance(self, items, depth, force_refresh=False):
- """
- Retrieves all course metadata inheritance trees needed to load items
- """
-
- locations = [
- Location(item['location']) for item in items
- if not (item['location']['category'] == 'course' and depth == 0)
- ]
- return self.get_cached_metadata_inheritance_trees(locations, force_refresh=force_refresh)
-
- def _load_item(self, item, data_cache, metadata_cache):
+ def _load_item(self, item, data_cache, apply_cached_metadata=True):
"""
Load an XModuleDescriptor from item, using the children stored in data_cache
"""
@@ -434,6 +432,10 @@ class MongoModuleStore(ModuleStoreBase):
resource_fs = OSFS(root)
+ cached_metadata = {}
+ if apply_cached_metadata:
+ cached_metadata = self.get_cached_metadata_inheritance_tree(Location(item['location']))
+
# TODO (cdodge): When the 'split module store' work has been completed, we should remove
# the 'metadata_inheritance_tree' parameter
system = CachingDescriptorSystem(
@@ -443,7 +445,7 @@ class MongoModuleStore(ModuleStoreBase):
resource_fs,
self.error_tracker,
self.render_template,
- metadata_cache,
+ cached_metadata,
)
return system.load_item(item['location'])
@@ -453,11 +455,11 @@ class MongoModuleStore(ModuleStoreBase):
to specified depth
"""
data_cache = self._cache_children(items, depth)
- inheritance_cache = self._cache_metadata_inheritance(items, depth)
# if we are loading a course object, if we're not prefetching children (depth != 0) then don't
- # bother with the metadata inheritence
- return [self._load_item(item, data_cache, inheritance_cache) for item in items]
+ # bother with the metadata inheritance
+ return [self._load_item(item, data_cache,
+ apply_cached_metadata=(item['location']['category']!='course' or depth !=0)) for item in items]
def get_courses(self):
'''
diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py b/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py
index 3e29c07ea4..061d70d09f 100644
--- a/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py
+++ b/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py
@@ -103,58 +103,3 @@ class TestMongoModuleStore(object):
def test_path_to_location(self):
'''Make sure that path_to_location works'''
check_path_to_location(self.store)
-
- def test_metadata_inheritance_query_count(self):
- '''
- When retrieving items from mongo, we should only query the cache a number of times
- equal to the number of courses being retrieved from.
-
- We should also not query
- '''
- self.store.metadata_inheritance_cache = Mock()
- get_many = self.store.metadata_inheritance_cache.get_many
- set_many = self.store.metadata_inheritance_cache.set_many
- get_many.return_value = {('edX', 'toy'): {}}
-
- self.store.get_item(Location("i4x://edX/toy/course/2012_Fall"), depth=0)
- assert_false(get_many.called)
- assert_false(set_many.called)
- get_many.reset_mock()
-
- self.store.get_item(Location("i4x://edX/toy/course/2012_Fall"), depth=3)
- get_many.assert_called_with([('edX', 'toy')])
- assert_equals(0, set_many.call_count)
- get_many.reset_mock()
-
- self.store.get_items(Location('i4x', 'edX', None, 'course', None), depth=0)
- assert_false(get_many.called)
- assert_false(set_many.called)
- get_many.reset_mock()
-
- self.store.get_items(Location('i4x', 'edX', None, 'course', None), depth=3)
- assert_equals(1, get_many.call_count)
- assert_equals([('edX', 'simple'), ('edX', 'toy')], sorted(get_many.call_args[0][0]))
- assert_equals(1, set_many.call_count)
- assert_equals([('edX', 'simple')], sorted(set_many.call_args[0][0].keys()))
- get_many.reset_mock()
-
- self.store.get_items(Location('i4x', 'edX', None, None, None), depth=0)
- assert_equals(1, get_many.call_count)
- assert_equals([('edX', 'simple'), ('edX', 'toy')], sorted(get_many.call_args[0][0]))
- assert_equals(1, set_many.call_count)
- assert_equals([('edX', 'simple')], sorted(set_many.call_args[0][0].keys()))
- get_many.reset_mock()
-
- def test_metadata_inheritance_query_count_forced_refresh(self):
- self.store.metadata_inheritance_cache = Mock()
- get_many = self.store.metadata_inheritance_cache.get_many
- set_many = self.store.metadata_inheritance_cache.set_many
- get_many.return_value = {('edX', 'toy'): {}}
-
- self.store.get_cached_metadata_inheritance_trees(
- [Location("i4x://edX/toy/course/2012_Fall"), Location("i4x://edX/simple/course/2012_Fall")],
- True
- )
- assert_false(get_many.called)
- assert_equals(1, set_many.call_count)
- assert_equals([('edX', 'simple'), ('edX', 'toy')], sorted(set_many.call_args[0][0].keys()))
diff --git a/lms/djangoapps/course_wiki/tests/tests.py b/lms/djangoapps/course_wiki/tests/tests.py
index cecc4f9cf9..620cf104d7 100644
--- a/lms/djangoapps/course_wiki/tests/tests.py
+++ b/lms/djangoapps/course_wiki/tests/tests.py
@@ -3,13 +3,11 @@ from django.test.utils import override_settings
import xmodule.modulestore.django
-from courseware.tests.tests import PageLoader, TEST_DATA_XML_MODULESTORE
+from courseware.tests.tests import LoginEnrollmentTestCase, TEST_DATA_XML_MODULESTORE
from xmodule.modulestore.django import modulestore
-from xmodule.modulestore.xml_importer import import_from_xml
-
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
-class WikiRedirectTestCase(PageLoader):
+class WikiRedirectTestCase(LoginEnrollmentTestCase):
def setUp(self):
xmodule.modulestore.django._MODULESTORES = {}
courses = modulestore().get_courses()
@@ -30,8 +28,6 @@ class WikiRedirectTestCase(PageLoader):
self.activate_user(self.student)
self.activate_user(self.instructor)
-
-
def test_wiki_redirect(self):
"""
Test that requesting wiki URLs redirect properly to or out of classes.
@@ -69,7 +65,6 @@ class WikiRedirectTestCase(PageLoader):
self.assertEqual(resp.status_code, 302)
self.assertEqual(resp['Location'], 'http://testserver' + destination)
-
def create_course_page(self, course):
"""
Test that loading the course wiki page creates the wiki page.
@@ -98,7 +93,6 @@ class WikiRedirectTestCase(PageLoader):
self.assertTrue("course info" in resp.content.lower())
self.assertTrue("courseware" in resp.content.lower())
-
def test_course_navigator(self):
""""
Test that going from a course page to a wiki page contains the course navigator.
@@ -108,7 +102,6 @@ class WikiRedirectTestCase(PageLoader):
self.enroll(self.toy)
self.create_course_page(self.toy)
-
course_wiki_page = reverse('wiki:get', kwargs={'path': self.toy.wiki_slug + '/'})
referer = reverse("courseware", kwargs={'course_id': self.toy.id})
diff --git a/lms/djangoapps/courseware/features/common.py b/lms/djangoapps/courseware/features/common.py
index 7d41637c8e..f6256adfa1 100644
--- a/lms/djangoapps/courseware/features/common.py
+++ b/lms/djangoapps/courseware/features/common.py
@@ -1,3 +1,6 @@
+#pylint: disable=C0111
+#pylint: disable=W0621
+
from lettuce import world, step
from nose.tools import assert_equals, assert_in
from lettuce.django import django_url
@@ -6,83 +9,13 @@ from student.models import CourseEnrollment
from xmodule.modulestore import Location
from xmodule.modulestore.django import _MODULESTORES, modulestore
from xmodule.templates import update_templates
-import time
+from xmodule.course_module import CourseDescriptor
+from courseware.courses import get_course_by_id
+from xmodule import seq_module, vertical_module
from logging import getLogger
logger = getLogger(__name__)
-
-@step(u'I wait (?:for )?"(\d+)" seconds?$')
-def wait(step, seconds):
- time.sleep(float(seconds))
-
-
-@step('I (?:visit|access|open) the homepage$')
-def i_visit_the_homepage(step):
- world.browser.visit(django_url('/'))
- assert world.browser.is_element_present_by_css('header.global', 10)
-
-
-@step(u'I (?:visit|access|open) the dashboard$')
-def i_visit_the_dashboard(step):
- world.browser.visit(django_url('/dashboard'))
- assert world.browser.is_element_present_by_css('section.container.dashboard', 5)
-
-
-@step(r'click (?:the|a) link (?:called|with the text) "([^"]*)"$')
-def click_the_link_called(step, text):
- world.browser.find_link_by_text(text).click()
-
-
-@step('I should be on the dashboard page$')
-def i_should_be_on_the_dashboard(step):
- assert world.browser.is_element_present_by_css('section.container.dashboard', 5)
- assert world.browser.title == 'Dashboard'
-
-
-@step(u'I (?:visit|access|open) the courses page$')
-def i_am_on_the_courses_page(step):
- world.browser.visit(django_url('/courses'))
- assert world.browser.is_element_present_by_css('section.courses')
-
-
-@step('I should see that the path is "([^"]*)"$')
-def i_should_see_that_the_path_is(step, path):
- assert world.browser.url == django_url(path)
-
-
-@step(u'the page title should be "([^"]*)"$')
-def the_page_title_should_be(step, title):
- assert world.browser.title == title
-
-
-@step(r'should see that the url is "([^"]*)"$')
-def should_have_the_url(step, url):
- assert_equals(world.browser.url, url)
-
-
-@step(r'should see (?:the|a) link (?:called|with the text) "([^"]*)"$')
-def should_see_a_link_called(step, text):
- assert len(world.browser.find_link_by_text(text)) > 0
-
-
-@step(r'should see "(.*)" (?:somewhere|anywhere) in (?:the|this) page')
-def should_see_in_the_page(step, text):
- assert_in(text, world.browser.html)
-
-
-@step('I am logged in$')
-def i_am_logged_in(step):
- world.create_user('robot')
- world.log_in('robot', 'test')
- world.browser.visit(django_url('/'))
-
-
-@step('I am not logged in$')
-def i_am_not_logged_in(step):
- world.browser.cookies.delete()
-
-
TEST_COURSE_ORG = 'edx'
TEST_COURSE_NAME = 'Test Course'
TEST_SECTION_NAME = "Problem"
@@ -94,7 +27,7 @@ def create_course(step, course):
# First clear the modulestore so we don't try to recreate
# the same course twice
# This also ensures that the necessary templates are loaded
- flush_xmodule_store()
+ world.clear_courses()
# Create the course
# We always use the same org and display name,
@@ -135,29 +68,6 @@ def add_tab_to_course(step, course, extra_tab_name):
display_name=str(extra_tab_name))
-@step(u'I am an edX user$')
-def i_am_an_edx_user(step):
- world.create_user('robot')
-
-
-@step(u'User "([^"]*)" is an edX user$')
-def registered_edx_user(step, uname):
- world.create_user(uname)
-
-
-def flush_xmodule_store():
- # Flush and initialize the module store
- # It needs the templates because it creates new records
- # by cloning from the template.
- # Note that if your test module gets in some weird state
- # (though it shouldn't), do this manually
- # from the bash shell to drop it:
- # $ mongo test_xmodule --eval "db.dropDatabase()"
- _MODULESTORES = {}
- modulestore().collection.drop()
- update_templates()
-
-
def course_id(course_num):
return "%s/%s/%s" % (TEST_COURSE_ORG, course_num,
TEST_COURSE_NAME.replace(" ", "_"))
@@ -177,3 +87,87 @@ def section_location(course_num):
course=course_num,
category='sequential',
name=TEST_SECTION_NAME.replace(" ", "_"))
+
+
+def get_courses():
+ '''
+ Returns dict of lists of courses available, keyed by course.org (ie university).
+ Courses are sorted by course.number.
+ '''
+ courses = [c for c in modulestore().get_courses()
+ if isinstance(c, CourseDescriptor)]
+ courses = sorted(courses, key=lambda course: course.number)
+ return courses
+
+
+def get_courseware_with_tabs(course_id):
+ """
+ Given a course_id (string), return a courseware array of dictionaries for the
+ top three levels of navigation. Same as get_courseware() except include
+ the tabs on the right hand main navigation page.
+
+ This hides the appropriate courseware as defined by the hide_from_toc field:
+ chapter.lms.hide_from_toc
+
+ Example:
+
+ [{
+ 'chapter_name': 'Overview',
+ 'sections': [{
+ 'clickable_tab_count': 0,
+ 'section_name': 'Welcome',
+ 'tab_classes': []
+ }, {
+ 'clickable_tab_count': 1,
+ 'section_name': 'System Usage Sequence',
+ 'tab_classes': ['VerticalDescriptor']
+ }, {
+ 'clickable_tab_count': 0,
+ 'section_name': 'Lab0: Using the tools',
+ 'tab_classes': ['HtmlDescriptor', 'HtmlDescriptor', 'CapaDescriptor']
+ }, {
+ 'clickable_tab_count': 0,
+ 'section_name': 'Circuit Sandbox',
+ 'tab_classes': []
+ }]
+ }, {
+ 'chapter_name': 'Week 1',
+ 'sections': [{
+ 'clickable_tab_count': 4,
+ 'section_name': 'Administrivia and Circuit Elements',
+ 'tab_classes': ['VerticalDescriptor', 'VerticalDescriptor', 'VerticalDescriptor', 'VerticalDescriptor']
+ }, {
+ 'clickable_tab_count': 0,
+ 'section_name': 'Basic Circuit Analysis',
+ 'tab_classes': ['CapaDescriptor', 'CapaDescriptor', 'CapaDescriptor']
+ }, {
+ 'clickable_tab_count': 0,
+ 'section_name': 'Resistor Divider',
+ 'tab_classes': []
+ }, {
+ 'clickable_tab_count': 0,
+ 'section_name': 'Week 1 Tutorials',
+ 'tab_classes': []
+ }]
+ }, {
+ 'chapter_name': 'Midterm Exam',
+ 'sections': [{
+ 'clickable_tab_count': 2,
+ 'section_name': 'Midterm Exam',
+ 'tab_classes': ['VerticalDescriptor', 'VerticalDescriptor']
+ }]
+ }]
+ """
+
+ course = get_course_by_id(course_id)
+ chapters = [chapter for chapter in course.get_children() if not chapter.lms.hide_from_toc]
+ courseware = [{'chapter_name': c.display_name_with_default,
+ 'sections': [{'section_name': s.display_name_with_default,
+ 'clickable_tab_count': len(s.get_children()) if (type(s) == seq_module.SequenceDescriptor) else 0,
+ 'tabs': [{'children_count': len(t.get_children()) if (type(t) == vertical_module.VerticalDescriptor) else 0,
+ 'class': t.__class__.__name__}
+ for t in s.get_children()]}
+ for s in c.get_children() if not s.lms.hide_from_toc]}
+ for c in chapters]
+
+ return courseware
diff --git a/lms/djangoapps/courseware/features/courses.py b/lms/djangoapps/courseware/features/courses.py
deleted file mode 100644
index c99fb58b85..0000000000
--- a/lms/djangoapps/courseware/features/courses.py
+++ /dev/null
@@ -1,234 +0,0 @@
-from lettuce import world
-from xmodule.course_module import CourseDescriptor
-from xmodule.modulestore.django import modulestore
-from courseware.courses import get_course_by_id
-from xmodule import seq_module, vertical_module
-
-from logging import getLogger
-logger = getLogger(__name__)
-
-## support functions
-
-
-def get_courses():
- '''
- Returns dict of lists of courses available, keyed by course.org (ie university).
- Courses are sorted by course.number.
- '''
- courses = [c for c in modulestore().get_courses()
- if isinstance(c, CourseDescriptor)]
- courses = sorted(courses, key=lambda course: course.number)
- return courses
-
-
-def get_courseware_with_tabs(course_id):
- """
- Given a course_id (string), return a courseware array of dictionaries for the
- top three levels of navigation. Same as get_courseware() except include
- the tabs on the right hand main navigation page.
-
- This hides the appropriate courseware as defined by the hide_from_toc field:
- chapter.lms.hide_from_toc
-
- Example:
-
- [{
- 'chapter_name': 'Overview',
- 'sections': [{
- 'clickable_tab_count': 0,
- 'section_name': 'Welcome',
- 'tab_classes': []
- }, {
- 'clickable_tab_count': 1,
- 'section_name': 'System Usage Sequence',
- 'tab_classes': ['VerticalDescriptor']
- }, {
- 'clickable_tab_count': 0,
- 'section_name': 'Lab0: Using the tools',
- 'tab_classes': ['HtmlDescriptor', 'HtmlDescriptor', 'CapaDescriptor']
- }, {
- 'clickable_tab_count': 0,
- 'section_name': 'Circuit Sandbox',
- 'tab_classes': []
- }]
- }, {
- 'chapter_name': 'Week 1',
- 'sections': [{
- 'clickable_tab_count': 4,
- 'section_name': 'Administrivia and Circuit Elements',
- 'tab_classes': ['VerticalDescriptor', 'VerticalDescriptor', 'VerticalDescriptor', 'VerticalDescriptor']
- }, {
- 'clickable_tab_count': 0,
- 'section_name': 'Basic Circuit Analysis',
- 'tab_classes': ['CapaDescriptor', 'CapaDescriptor', 'CapaDescriptor']
- }, {
- 'clickable_tab_count': 0,
- 'section_name': 'Resistor Divider',
- 'tab_classes': []
- }, {
- 'clickable_tab_count': 0,
- 'section_name': 'Week 1 Tutorials',
- 'tab_classes': []
- }]
- }, {
- 'chapter_name': 'Midterm Exam',
- 'sections': [{
- 'clickable_tab_count': 2,
- 'section_name': 'Midterm Exam',
- 'tab_classes': ['VerticalDescriptor', 'VerticalDescriptor']
- }]
- }]
- """
-
- course = get_course_by_id(course_id)
- chapters = [chapter for chapter in course.get_children() if not chapter.lms.hide_from_toc]
- courseware = [{'chapter_name': c.display_name_with_default,
- 'sections': [{'section_name': s.display_name_with_default,
- 'clickable_tab_count': len(s.get_children()) if (type(s) == seq_module.SequenceDescriptor) else 0,
- 'tabs': [{'children_count': len(t.get_children()) if (type(t) == vertical_module.VerticalDescriptor) else 0,
- 'class': t.__class__.__name__}
- for t in s.get_children()]}
- for s in c.get_children() if not s.lms.hide_from_toc]}
- for c in chapters]
-
- return courseware
-
-
-def process_section(element, num_tabs=0):
- '''
- Process section reads through whatever is in 'course-content' and classifies it according to sequence module type.
-
- This function is recursive
-
- There are 6 types, with 6 actions.
-
- Sequence Module
- -contains one child module
-
- Vertical Module
- -contains other modules
- -process it and get its children, then process them
-
- Capa Module
- -problem type, contains only one problem
- -for this, the most complex type, we created a separate method, process_problem
-
- Video Module
- -video type, contains only one video
- -we only check to ensure that a section with class of video exists
-
- HTML Module
- -html text
- -we do not check anything about it
-
- Custom Tag Module
- -a custom 'hack' module type
- -there is a large variety of content that could go in a custom tag module, so we just pass if it is of this unusual type
-
- can be used like this:
- e = world.browser.find_by_css('section.course-content section')
- process_section(e)
-
- '''
- if element.has_class('xmodule_display xmodule_SequenceModule'):
- logger.debug('####### Processing xmodule_SequenceModule')
- child_modules = element.find_by_css("div>div>section[class^='xmodule']")
- for mod in child_modules:
- process_section(mod)
-
- elif element.has_class('xmodule_display xmodule_VerticalModule'):
- logger.debug('####### Processing xmodule_VerticalModule')
- vert_list = element.find_by_css("li section[class^='xmodule']")
- for item in vert_list:
- process_section(item)
-
- elif element.has_class('xmodule_display xmodule_CapaModule'):
- logger.debug('####### Processing xmodule_CapaModule')
- assert element.find_by_css("section[id^='problem']"), "No problems found in Capa Module"
- p = element.find_by_css("section[id^='problem']").first
- p_id = p['id']
- logger.debug('####################')
- logger.debug('id is "%s"' % p_id)
- logger.debug('####################')
- process_problem(p, p_id)
-
- elif element.has_class('xmodule_display xmodule_VideoModule'):
- logger.debug('####### Processing xmodule_VideoModule')
- assert element.find_by_css("section[class^='video']"), "No video found in Video Module"
-
- elif element.has_class('xmodule_display xmodule_HtmlModule'):
- logger.debug('####### Processing xmodule_HtmlModule')
- pass
-
- elif element.has_class('xmodule_display xmodule_CustomTagModule'):
- logger.debug('####### Processing xmodule_CustomTagModule')
- pass
-
- else:
- assert False, "Class for element not recognized!!"
-
-
-def process_problem(element, problem_id):
- '''
- Process problem attempts to
- 1) scan all the input fields and reset them
- 2) click the 'check' button and look for an incorrect response (p.status text should be 'incorrect')
- 3) click the 'show answer' button IF it exists and IF the answer is not already displayed
- 4) enter the correct answer in each input box
- 5) click the 'check' button and verify that answers are correct
-
- Because of all the ajax calls happening, sometimes the test fails because objects disconnect from the DOM.
- The basic functionality does exist, though, and I'm hoping that someone can take it over and make it super effective.
- '''
-
- prob_xmod = element.find_by_css("section.problem").first
- input_fields = prob_xmod.find_by_css("section[id^='input']")
-
- ## clear out all input to ensure an incorrect result
- for field in input_fields:
- field.find_by_css("input").first.fill('')
-
- ## because of cookies or the application, only click the 'check' button if the status is not already 'incorrect'
- # This would need to be reworked because multiple choice problems don't have this status
- # if prob_xmod.find_by_css("p.status").first.text.strip().lower() != 'incorrect':
- prob_xmod.find_by_css("section.action input.check").first.click()
-
- ## all elements become disconnected after the click
- ## grab element and prob_xmod because the dom has changed (some classes/elements became hidden and changed the hierarchy)
- # Wait for the ajax reload
- assert world.browser.is_element_present_by_css("section[id='%s']" % problem_id, wait_time=5)
- element = world.browser.find_by_css("section[id='%s']" % problem_id).first
- prob_xmod = element.find_by_css("section.problem").first
- input_fields = prob_xmod.find_by_css("section[id^='input']")
- for field in input_fields:
- assert field.find_by_css("div.incorrect"), "The 'check' button did not work for %s" % (problem_id)
-
- show_button = element.find_by_css("section.action input.show").first
- ## this logic is to ensure we do not accidentally hide the answers
- if show_button.value.lower() == 'show answer':
- show_button.click()
- else:
- pass
-
- ## grab element and prob_xmod because the dom has changed (some classes/elements became hidden and changed the hierarchy)
- assert world.browser.is_element_present_by_css("section[id='%s']" % problem_id, wait_time=5)
- element = world.browser.find_by_css("section[id='%s']" % problem_id).first
- prob_xmod = element.find_by_css("section.problem").first
- input_fields = prob_xmod.find_by_css("section[id^='input']")
-
- ## in each field, find the answer, and send it to the field.
- ## Note that this does not work if the answer type is a strange format, e.g. "either a or b"
- for field in input_fields:
- field.find_by_css("input").first.fill(field.find_by_css("p[id^='answer']").first.text)
-
- prob_xmod.find_by_css("section.action input.check").first.click()
-
- ## assert that we entered the correct answers
- ## grab element and prob_xmod because the dom has changed (some classes/elements became hidden and changed the hierarchy)
- assert world.browser.is_element_present_by_css("section[id='%s']" % problem_id, wait_time=5)
- element = world.browser.find_by_css("section[id='%s']" % problem_id).first
- prob_xmod = element.find_by_css("section.problem").first
- input_fields = prob_xmod.find_by_css("section[id^='input']")
- for field in input_fields:
- ## if you don't use 'starts with ^=' the test will fail because the actual class is 'correct ' (with a space)
- assert field.find_by_css("div[class^='correct']"), "The check answer values were not correct for %s" % problem_id
diff --git a/lms/djangoapps/courseware/features/courseware.py b/lms/djangoapps/courseware/features/courseware.py
index 7e99cc9f55..234f3a84d2 100644
--- a/lms/djangoapps/courseware/features/courseware.py
+++ b/lms/djangoapps/courseware/features/courseware.py
@@ -1,3 +1,6 @@
+#pylint: disable=C0111
+#pylint: disable=W0621
+
from lettuce import world, step
from lettuce.django import django_url
diff --git a/lms/djangoapps/courseware/features/courseware_common.py b/lms/djangoapps/courseware/features/courseware_common.py
index 96304e016f..4e9aa3fb7b 100644
--- a/lms/djangoapps/courseware/features/courseware_common.py
+++ b/lms/djangoapps/courseware/features/courseware_common.py
@@ -1,23 +1,23 @@
+#pylint: disable=C0111
+#pylint: disable=W0621
+
from lettuce import world, step
-from lettuce.django import django_url
@step('I click on View Courseware')
def i_click_on_view_courseware(step):
- css = 'a.enter-course'
- world.browser.find_by_css(css).first.click()
+ world.css_click('a.enter-course')
@step('I click on the "([^"]*)" tab$')
-def i_click_on_the_tab(step, tab):
- world.browser.find_link_by_partial_text(tab).first.click()
+def i_click_on_the_tab(step, tab_text):
+ world.click_link(tab_text)
world.save_the_html()
@step('I visit the courseware URL$')
def i_visit_the_course_info_url(step):
- url = django_url('/courses/MITx/6.002x/2012_Fall/courseware')
- world.browser.visit(url)
+ world.visit('/courses/MITx/6.002x/2012_Fall/courseware')
@step(u'I do not see "([^"]*)" anywhere on the page')
@@ -27,18 +27,15 @@ def i_do_not_see_text_anywhere_on_the_page(step, text):
@step(u'I am on the dashboard page$')
def i_am_on_the_dashboard_page(step):
- assert world.browser.is_element_present_by_css('section.courses')
- assert world.browser.url == django_url('/dashboard')
+ assert world.is_css_present('section.courses')
+ assert world.url_equals('/dashboard')
@step('the "([^"]*)" tab is active$')
-def the_tab_is_active(step, tab):
- css = '.course-tabs a.active'
- active_tab = world.browser.find_by_css(css)
- assert (active_tab.text == tab)
+def the_tab_is_active(step, tab_text):
+ assert world.css_text('.course-tabs a.active') == tab_text
@step('the login dialog is visible$')
def login_dialog_visible(step):
- css = 'form#login_form.login_form'
- assert world.browser.find_by_css(css).visible
+ assert world.css_visible('form#login_form.login_form')
diff --git a/lms/djangoapps/courseware/features/high-level-tabs.feature b/lms/djangoapps/courseware/features/high-level-tabs.feature
index 473f3f1572..c60ec7b374 100644
--- a/lms/djangoapps/courseware/features/high-level-tabs.feature
+++ b/lms/djangoapps/courseware/features/high-level-tabs.feature
@@ -3,7 +3,7 @@ Feature: All the high level tabs should work
As a student
I want to navigate through the high level tabs
-Scenario: I can navigate to all high -level tabs in a course
+Scenario: I can navigate to all high - level tabs in a course
Given: I am registered for the course "6.002x"
And The course "6.002x" has extra tab "Custom Tab"
And I am logged in
diff --git a/lms/djangoapps/courseware/features/homepage.py b/lms/djangoapps/courseware/features/homepage.py
index 442098c161..62e9096e70 100644
--- a/lms/djangoapps/courseware/features/homepage.py
+++ b/lms/djangoapps/courseware/features/homepage.py
@@ -1,3 +1,6 @@
+#pylint: disable=C0111
+#pylint: disable=W0621
+
from lettuce import world, step
from nose.tools import assert_in
diff --git a/lms/djangoapps/courseware/features/login.py b/lms/djangoapps/courseware/features/login.py
index 094db078ca..bc90ea301c 100644
--- a/lms/djangoapps/courseware/features/login.py
+++ b/lms/djangoapps/courseware/features/login.py
@@ -1,3 +1,6 @@
+#pylint: disable=C0111
+#pylint: disable=W0621
+
from lettuce import step, world
from django.contrib.auth.models import User
@@ -28,9 +31,7 @@ def i_should_see_the_login_error_message(step, msg):
@step(u'click the dropdown arrow$')
def click_the_dropdown(step):
- css = ".dropdown"
- e = world.browser.find_by_css(css)
- e.click()
+ world.css_click('.dropdown')
#### helper functions
diff --git a/lms/djangoapps/courseware/features/openended.py b/lms/djangoapps/courseware/features/openended.py
index 0725a051ff..d848eb55d7 100644
--- a/lms/djangoapps/courseware/features/openended.py
+++ b/lms/djangoapps/courseware/features/openended.py
@@ -1,3 +1,6 @@
+#pylint: disable=C0111
+#pylint: disable=W0621
+
from lettuce import world, step
from lettuce.django import django_url
from nose.tools import assert_equals, assert_in
@@ -12,7 +15,7 @@ def navigate_to_an_openended_question(step):
problem = '/courses/MITx/3.091x/2012_Fall/courseware/Week_10/Polymer_Synthesis/'
world.browser.visit(django_url(problem))
tab_css = 'ol#sequence-list > li > a[data-element="5"]'
- world.browser.find_by_css(tab_css).click()
+ world.css_click(tab_css)
@step('I navigate to an openended question as staff$')
@@ -22,81 +25,69 @@ def navigate_to_an_openended_question_as_staff(step):
problem = '/courses/MITx/3.091x/2012_Fall/courseware/Week_10/Polymer_Synthesis/'
world.browser.visit(django_url(problem))
tab_css = 'ol#sequence-list > li > a[data-element="5"]'
- world.browser.find_by_css(tab_css).click()
+ world.css_click(tab_css)
@step(u'I enter the answer "([^"]*)"$')
def enter_the_answer_text(step, text):
- textarea_css = 'textarea'
- world.browser.find_by_css(textarea_css).first.fill(text)
+ world.css_fill('textarea', text)
@step(u'I submit the answer "([^"]*)"$')
def i_submit_the_answer_text(step, text):
- textarea_css = 'textarea'
- world.browser.find_by_css(textarea_css).first.fill(text)
- check_css = 'input.check'
- world.browser.find_by_css(check_css).click()
+ world.css_fill('textarea', text)
+ world.css_click('input.check')
@step('I click the link for full output$')
def click_full_output_link(step):
- link_css = 'a.full'
- world.browser.find_by_css(link_css).first.click()
+ world.css_click('a.full')
@step(u'I visit the staff grading page$')
def i_visit_the_staff_grading_page(step):
- # course_u = '/courses/MITx/3.091x/2012_Fall'
- # sg_url = '%s/staff_grading' % course_u
- world.browser.click_link_by_text('Instructor')
- world.browser.click_link_by_text('Staff grading')
- # world.browser.visit(django_url(sg_url))
+ world.click_link('Instructor')
+ world.click_link('Staff grading')
@step(u'I see the grader message "([^"]*)"$')
def see_grader_message(step, msg):
message_css = 'div.external-grader-message'
- grader_msg = world.browser.find_by_css(message_css).text
- assert_in(msg, grader_msg)
+ assert_in(msg, world.css_text(message_css))
@step(u'I see the grader status "([^"]*)"$')
def see_the_grader_status(step, status):
status_css = 'div.grader-status'
- grader_status = world.browser.find_by_css(status_css).text
- assert_equals(status, grader_status)
+ assert_equals(status, world.css_text(status_css))
@step('I see the red X$')
def see_the_red_x(step):
- x_css = 'div.grader-status > span.incorrect'
- assert world.browser.find_by_css(x_css)
+ assert world.is_css_present('div.grader-status > span.incorrect')
@step(u'I see the grader score "([^"]*)"$')
def see_the_grader_score(step, score):
score_css = 'div.result-output > p'
- score_text = world.browser.find_by_css(score_css).text
+ score_text = world.css_text(score_css)
assert_equals(score_text, 'Score: %s' % score)
@step('I see the link for full output$')
def see_full_output_link(step):
- link_css = 'a.full'
- assert world.browser.find_by_css(link_css)
+ assert world.is_css_present('a.full')
@step('I see the spelling grading message "([^"]*)"$')
def see_spelling_msg(step, msg):
- spelling_css = 'div.spelling'
- spelling_msg = world.browser.find_by_css(spelling_css).text
+ spelling_msg = world.css_text('div.spelling')
assert_equals('Spelling: %s' % msg, spelling_msg)
@step(u'my answer is queued for instructor grading$')
def answer_is_queued_for_instructor_grading(step):
list_css = 'ul.problem-list > li > a'
- actual_msg = world.browser.find_by_css(list_css).text
+ actual_msg = world.css_text(list_css)
expected_msg = "(0 graded, 1 pending)"
assert_in(expected_msg, actual_msg)
diff --git a/lms/djangoapps/courseware/features/problems.py b/lms/djangoapps/courseware/features/problems.py
index d2d379a212..b25d606c4e 100644
--- a/lms/djangoapps/courseware/features/problems.py
+++ b/lms/djangoapps/courseware/features/problems.py
@@ -2,6 +2,8 @@
Steps for problem.feature lettuce tests
'''
+#pylint: disable=C0111
+#pylint: disable=W0621
from lettuce import world, step
from lettuce.django import django_url
@@ -339,7 +341,7 @@ def assert_answer_mark(step, problem_type, correctness):
# At least one of the correct selectors should be present
for sel in selector_dict[problem_type]:
- has_expected = world.browser.is_element_present_by_css(sel, wait_time=4)
+ has_expected = world.is_css_present(sel)
# As soon as we find the selector, break out of the loop
if has_expected:
@@ -366,7 +368,7 @@ def inputfield(problem_type, choice=None, input_num=1):
# If the input element doesn't exist, fail immediately
- assert(world.browser.is_element_present_by_css(sel, wait_time=4))
+ assert world.is_css_present(sel)
# Retrieve the input element
return world.browser.find_by_css(sel)
diff --git a/lms/djangoapps/courseware/features/registration.py b/lms/djangoapps/courseware/features/registration.py
index 94b9b50f6c..72bde65f99 100644
--- a/lms/djangoapps/courseware/features/registration.py
+++ b/lms/djangoapps/courseware/features/registration.py
@@ -1,3 +1,6 @@
+#pylint: disable=C0111
+#pylint: disable=W0621
+
from lettuce import world, step
from lettuce.django import django_url
from common import TEST_COURSE_ORG, TEST_COURSE_NAME
@@ -13,17 +16,17 @@ def i_register_for_the_course(step, course):
register_link = intro_section.find_by_css('a.register')
register_link.click()
- assert world.browser.is_element_present_by_css('section.container.dashboard')
+ assert world.is_css_present('section.container.dashboard')
@step(u'I should see the course numbered "([^"]*)" in my dashboard$')
def i_should_see_that_course_in_my_dashboard(step, course):
course_link_css = 'section.my-courses a[href*="%s"]' % course
- assert world.browser.is_element_present_by_css(course_link_css)
+ assert world.is_css_present(course_link_css)
@step(u'I press the "([^"]*)" button in the Unenroll dialog')
def i_press_the_button_in_the_unenroll_dialog(step, value):
button_css = 'section#unenroll-modal input[value="%s"]' % value
- world.browser.find_by_css(button_css).click()
- assert world.browser.is_element_present_by_css('section.container.dashboard')
+ world.css_click(button_css)
+ assert world.is_css_present('section.container.dashboard')
diff --git a/lms/djangoapps/courseware/features/signup.py b/lms/djangoapps/courseware/features/signup.py
index 3a697a6102..5ba385ef54 100644
--- a/lms/djangoapps/courseware/features/signup.py
+++ b/lms/djangoapps/courseware/features/signup.py
@@ -1,5 +1,7 @@
-from lettuce import world, step
+#pylint: disable=C0111
+#pylint: disable=W0621
+from lettuce import world, step
@step('I fill in "([^"]*)" on the registration form with "([^"]*)"$')
def when_i_fill_in_field_on_the_registration_form_with_value(step, field, value):
@@ -22,4 +24,4 @@ def i_check_checkbox(step, checkbox):
@step('I should see "([^"]*)" in the dashboard banner$')
def i_should_see_text_in_the_dashboard_banner_section(step, text):
css_selector = "section.dashboard-banner h2"
- assert (text in world.browser.find_by_css(css_selector).text)
+ assert (text in world.css_text(css_selector))
diff --git a/lms/djangoapps/courseware/features/smart-accordion.py b/lms/djangoapps/courseware/features/smart-accordion.py
index a7eb782722..63408d7683 100644
--- a/lms/djangoapps/courseware/features/smart-accordion.py
+++ b/lms/djangoapps/courseware/features/smart-accordion.py
@@ -1,8 +1,11 @@
+#pylint: disable=C0111
+#pylint: disable=W0621
+
from lettuce import world, step
from re import sub
from nose.tools import assert_equals
from xmodule.modulestore.django import modulestore
-from courses import *
+from common import *
from logging import getLogger
logger = getLogger(__name__)
@@ -32,20 +35,20 @@ def i_verify_all_the_content_of_each_course(step):
pass
for test_course in registered_courses:
- test_course.find_by_css('a').click()
+ test_course.css_click('a')
check_for_errors()
# Get the course. E.g. 'MITx/6.002x/2012_Fall'
current_course = sub('/info', '', sub('.*/courses/', '', world.browser.url))
validate_course(current_course, ids)
- world.browser.find_link_by_text('Courseware').click()
- assert world.browser.is_element_present_by_id('accordion', wait_time=2)
+ world.click_link('Courseware')
+ assert world.is_css_present('accordion')
check_for_errors()
browse_course(current_course)
# clicking the user link gets you back to the user's home page
- world.browser.find_by_css('.user-link').click()
+ world.css_click('.user-link')
check_for_errors()
@@ -94,7 +97,7 @@ def browse_course(course_id):
world.browser.find_by_css('#accordion > nav > div')[chapter_it].find_by_tag('li')[section_it].find_by_tag('a').click()
## sometimes the course-content takes a long time to load
- assert world.browser.is_element_present_by_css('.course-content', wait_time=5)
+ assert world.is_css_present('.course-content')
## look for server error div
check_for_errors()
diff --git a/lms/djangoapps/courseware/features/xqueue_setup.py b/lms/djangoapps/courseware/features/xqueue_setup.py
index 23706941a9..90a68961ee 100644
--- a/lms/djangoapps/courseware/features/xqueue_setup.py
+++ b/lms/djangoapps/courseware/features/xqueue_setup.py
@@ -1,3 +1,6 @@
+#pylint: disable=C0111
+#pylint: disable=W0621
+
from courseware.mock_xqueue_server.mock_xqueue_server import MockXQueueServer
from lettuce import before, after, world
from django.conf import settings
diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py
index 90ca796a2f..85a65b7772 100644
--- a/lms/djangoapps/courseware/tests/test_module_render.py
+++ b/lms/djangoapps/courseware/tests/test_module_render.py
@@ -2,16 +2,16 @@ from mock import MagicMock
import json
from django.http import Http404, HttpResponse
+from django.core.urlresolvers import reverse
from django.conf import settings
from django.test import TestCase
from django.test.client import RequestFactory
-from django.core.urlresolvers import reverse
from django.test.utils import override_settings
from xmodule.modulestore.exceptions import ItemNotFoundError
-import courseware.module_render as render
from xmodule.modulestore.django import modulestore
-from courseware.tests.tests import PageLoader
+import courseware.module_render as render
+from courseware.tests.tests import LoginEnrollmentTestCase
from courseware.model_data import ModelDataCache
from .factories import UserFactory
@@ -38,7 +38,7 @@ TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR)
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
-class ModuleRenderTestCase(PageLoader):
+class ModuleRenderTestCase(LoginEnrollmentTestCase):
def setUp(self):
self.location = ['i4x', 'edX', 'toy', 'chapter', 'Overview']
self.course_id = 'edX/toy/2012_Fall'
@@ -54,10 +54,9 @@ class ModuleRenderTestCase(PageLoader):
mock_request = MagicMock()
mock_request.FILES.keys.return_value = ['file_id']
mock_request.FILES.getlist.return_value = ['file'] * (settings.MAX_FILEUPLOADS_PER_INPUT + 1)
- self.assertEquals(render.modx_dispatch(mock_request, 'dummy', self.location,
- 'dummy').content,
- json.dumps({'success': 'Submission aborted! Maximum %d files may be submitted at once' %
- settings.MAX_FILEUPLOADS_PER_INPUT}))
+ self.assertEquals(render.modx_dispatch(mock_request, 'dummy', self.location, 'dummy').content,
+ json.dumps({'success': 'Submission aborted! Maximum %d files may be submitted at once' %
+ settings.MAX_FILEUPLOADS_PER_INPUT}))
mock_request_2 = MagicMock()
mock_request_2.FILES.keys.return_value = ['file_id']
inputfile = Stub()
@@ -68,7 +67,7 @@ class ModuleRenderTestCase(PageLoader):
self.assertEquals(render.modx_dispatch(mock_request_2, 'dummy', self.location,
'dummy').content,
json.dumps({'success': 'Submission aborted! Your file "%s" is too large (max size: %d MB)' %
- (inputfile.name, settings.STUDENT_FILEUPLOAD_MAX_SIZE / (1000 ** 2))}))
+ (inputfile.name, settings.STUDENT_FILEUPLOAD_MAX_SIZE / (1000 ** 2))}))
mock_request_3 = MagicMock()
mock_request_3.POST.copy.return_value = {}
mock_request_3.FILES = False
@@ -79,10 +78,10 @@ class ModuleRenderTestCase(PageLoader):
self.assertRaises(ItemNotFoundError, render.modx_dispatch,
mock_request_3, 'dummy', self.location, 'toy')
self.assertRaises(Http404, render.modx_dispatch, mock_request_3, 'dummy',
- self.location, self.course_id)
+ self.location, self.course_id)
mock_request_3.POST.copy.return_value = {'position': 1}
self.assertIsInstance(render.modx_dispatch(mock_request_3, 'goto_position',
- self.location, self.course_id), HttpResponse)
+ self.location, self.course_id), HttpResponse)
def test_get_score_bucket(self):
self.assertEquals(render.get_score_bucket(0, 10), 'incorrect')
@@ -124,19 +123,19 @@ class TestTOC(TestCase):
self.toy_course.id, self.portal_user, self.toy_course, depth=2)
expected = ([{'active': True, 'sections':
- [{'url_name': 'Toy_Videos', 'display_name': u'Toy Videos', 'graded': True,
- 'format': u'Lecture Sequence', 'due': '', 'active': False},
- {'url_name': 'Welcome', 'display_name': u'Welcome', 'graded': True,
- 'format': '', 'due': '', 'active': False},
- {'url_name': 'video_123456789012', 'display_name': 'video 123456789012', 'graded': True,
- 'format': '', 'due': '', 'active': False},
- {'url_name': 'video_4f66f493ac8f', 'display_name': 'video 4f66f493ac8f', 'graded': True,
- 'format': '', 'due': '', 'active': False}],
- 'url_name': 'Overview', 'display_name': u'Overview'},
- {'active': False, 'sections':
- [{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True,
- 'format': '', 'due': '', 'active': False}],
- 'url_name': 'secret:magic', 'display_name': 'secret:magic'}])
+ [{'url_name': 'Toy_Videos', 'display_name': u'Toy Videos', 'graded': True,
+ 'format': u'Lecture Sequence', 'due': '', 'active': False},
+ {'url_name': 'Welcome', 'display_name': u'Welcome', 'graded': True,
+ 'format': '', 'due': '', 'active': False},
+ {'url_name': 'video_123456789012', 'display_name': 'video 123456789012', 'graded': True,
+ 'format': '', 'due': '', 'active': False},
+ {'url_name': 'video_4f66f493ac8f', 'display_name': 'video 4f66f493ac8f', 'graded': True,
+ 'format': '', 'due': '', 'active': False}],
+ 'url_name': 'Overview', 'display_name': u'Overview'},
+ {'active': False, 'sections':
+ [{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True,
+ 'format': '', 'due': '', 'active': False}],
+ 'url_name': 'secret:magic', 'display_name': 'secret:magic'}])
actual = render.toc_for_course(self.portal_user, request, self.toy_course, chapter, None, model_data_cache)
self.assertEqual(expected, actual)
@@ -151,19 +150,19 @@ class TestTOC(TestCase):
self.toy_course.id, self.portal_user, self.toy_course, depth=2)
expected = ([{'active': True, 'sections':
- [{'url_name': 'Toy_Videos', 'display_name': u'Toy Videos', 'graded': True,
- 'format': u'Lecture Sequence', 'due': '', 'active': False},
- {'url_name': 'Welcome', 'display_name': u'Welcome', 'graded': True,
- 'format': '', 'due': '', 'active': True},
- {'url_name': 'video_123456789012', 'display_name': 'video 123456789012', 'graded': True,
- 'format': '', 'due': '', 'active': False},
- {'url_name': 'video_4f66f493ac8f', 'display_name': 'video 4f66f493ac8f', 'graded': True,
- 'format': '', 'due': '', 'active': False}],
- 'url_name': 'Overview', 'display_name': u'Overview'},
- {'active': False, 'sections':
- [{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True,
- 'format': '', 'due': '', 'active': False}],
- 'url_name': 'secret:magic', 'display_name': 'secret:magic'}])
+ [{'url_name': 'Toy_Videos', 'display_name': u'Toy Videos', 'graded': True,
+ 'format': u'Lecture Sequence', 'due': '', 'active': False},
+ {'url_name': 'Welcome', 'display_name': u'Welcome', 'graded': True,
+ 'format': '', 'due': '', 'active': True},
+ {'url_name': 'video_123456789012', 'display_name': 'video 123456789012', 'graded': True,
+ 'format': '', 'due': '', 'active': False},
+ {'url_name': 'video_4f66f493ac8f', 'display_name': 'video 4f66f493ac8f', 'graded': True,
+ 'format': '', 'due': '', 'active': False}],
+ 'url_name': 'Overview', 'display_name': u'Overview'},
+ {'active': False, 'sections':
+ [{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True,
+ 'format': '', 'due': '', 'active': False}],
+ 'url_name': 'secret:magic', 'display_name': 'secret:magic'}])
actual = render.toc_for_course(self.portal_user, request, self.toy_course, chapter, section, model_data_cache)
self.assertEqual(expected, actual)
diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py
index cd845b1e44..9845477032 100644
--- a/lms/djangoapps/courseware/tests/tests.py
+++ b/lms/djangoapps/courseware/tests/tests.py
@@ -1,9 +1,6 @@
import logging
-log = logging.getLogger("mitx." + __name__)
-
import json
import time
-
from urlparse import urlsplit, urlunsplit
from django.contrib.auth.models import User, Group
@@ -29,29 +26,30 @@ from xmodule.modulestore.django import modulestore
from xmodule.modulestore import Location
from xmodule.modulestore.xml_importer import import_from_xml
from xmodule.modulestore.xml import XMLModuleStore
-from xmodule.timeparse import stringify_time
+log = logging.getLogger("mitx." + __name__)
def parse_json(response):
"""Parse response, which is assumed to be json"""
return json.loads(response.content)
-def user(email):
+def get_user(email):
'''look up a user by email'''
return User.objects.get(email=email)
-def registration(email):
+def get_registration(email):
'''look up registration object by email'''
return Registration.objects.get(user__email=email)
-# A bit of a hack--want mongo modulestore for these tests, until
-# jump_to works with the xmlmodulestore or we have an even better solution
-# NOTE: this means this test requires mongo to be running.
-
def mongo_store_config(data_dir):
+ '''
+ Defines default module store using MongoModuleStore
+
+ Use of this config requires mongo to be running
+ '''
return {
'default': {
'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore',
@@ -68,6 +66,7 @@ def mongo_store_config(data_dir):
def draft_mongo_store_config(data_dir):
+ '''Defines default module store using DraftMongoModuleStore'''
return {
'default': {
'ENGINE': 'xmodule.modulestore.mongo.DraftMongoModuleStore',
@@ -84,6 +83,7 @@ def draft_mongo_store_config(data_dir):
def xml_store_config(data_dir):
+ '''Defines default module store using XMLModuleStore'''
return {
'default': {
'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore',
@@ -100,8 +100,8 @@ TEST_DATA_MONGO_MODULESTORE = mongo_store_config(TEST_DATA_DIR)
TEST_DATA_DRAFT_MONGO_MODULESTORE = draft_mongo_store_config(TEST_DATA_DIR)
-class ActivateLoginTestCase(TestCase):
- '''Check that we can activate and log in'''
+class LoginEnrollmentTestCase(TestCase):
+ '''Base TestCase providing support for user creation, activation, login, and course enrollment'''
def assertRedirectsNoFollow(self, response, expected_url):
"""
@@ -117,32 +117,33 @@ class ActivateLoginTestCase(TestCase):
e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(expected_url)
if not (e_scheme or e_netloc):
- expected_url = urlunsplit(('http', 'testserver', e_path,
- e_query, e_fragment))
+ expected_url = urlunsplit(('http', 'testserver', e_path, e_query, e_fragment))
self.assertEqual(url, expected_url, "Response redirected to '{0}', expected '{1}'".format(
url, expected_url))
- def setUp(self):
- email = 'view@test.com'
- password = 'foo'
- self.create_account('viewtest', email, password)
- self.activate_user(email)
- self.login(email, password)
+ def setup_viewtest_user(self):
+ '''create a user account, activate, and log in'''
+ self.viewtest_email = 'view@test.com'
+ self.viewtest_password = 'foo'
+ self.viewtest_username = 'viewtest'
+ self.create_account(self.viewtest_username, self.viewtest_email, self.viewtest_password)
+ self.activate_user(self.viewtest_email)
+ self.login(self.viewtest_email, self.viewtest_password)
# ============ User creation and login ==============
- def _login(self, email, pw):
+ def _login(self, email, password):
'''Login. View should always return 200. The success/fail is in the
returned json'''
resp = self.client.post(reverse('login'),
- {'email': email, 'password': pw})
+ {'email': email, 'password': password})
self.assertEqual(resp.status_code, 200)
return resp
- def login(self, email, pw):
+ def login(self, email, password):
'''Login, check that it worked.'''
- resp = self._login(email, pw)
+ resp = self._login(email, password)
data = parse_json(resp)
self.assertTrue(data['success'])
return resp
@@ -154,34 +155,34 @@ class ActivateLoginTestCase(TestCase):
self.assertEqual(resp.status_code, 302)
return resp
- def _create_account(self, username, email, pw):
+ def _create_account(self, username, email, password):
'''Try to create an account. No error checking'''
resp = self.client.post('/create_account', {
'username': username,
'email': email,
- 'password': pw,
+ 'password': password,
'name': 'Fred Weasley',
'terms_of_service': 'true',
'honor_code': 'true',
})
return resp
- def create_account(self, username, email, pw):
+ def create_account(self, username, email, password):
'''Create the account and check that it worked'''
- resp = self._create_account(username, email, pw)
+ resp = self._create_account(username, email, password)
self.assertEqual(resp.status_code, 200)
data = parse_json(resp)
self.assertEqual(data['success'], True)
# Check both that the user is created, and inactive
- self.assertFalse(user(email).is_active)
+ self.assertFalse(get_user(email).is_active)
return resp
def _activate_user(self, email):
'''Look up the activation key for the user, then hit the activate view.
No error checking'''
- activation_key = registration(email).activation_key
+ activation_key = get_registration(email).activation_key
# and now we try to activate
resp = self.client.get(reverse('activate', kwargs={'key': activation_key}))
@@ -191,19 +192,7 @@ class ActivateLoginTestCase(TestCase):
resp = self._activate_user(email)
self.assertEqual(resp.status_code, 200)
# Now make sure that the user is now actually activated
- self.assertTrue(user(email).is_active)
-
- def test_activate_login(self):
- '''The setup function does all the work'''
- pass
-
- def test_logout(self):
- '''Setup function does login'''
- self.logout()
-
-
-class PageLoader(ActivateLoginTestCase):
- ''' Base class that adds a function to load all pages in a modulestore '''
+ self.assertTrue(get_user(email).is_active)
def _enroll(self, course):
"""Post to the enrollment view, and return the parsed json response"""
@@ -240,8 +229,7 @@ class PageLoader(ActivateLoginTestCase):
"""
resp = self.client.get(url)
self.assertEqual(resp.status_code, code,
- "got code {0} for url '{1}'. Expected code {2}"
- .format(resp.status_code, url, code))
+ "got code {0} for url '{1}'. Expected code {2}".format(resp.status_code, url, code))
return resp
def check_for_post_code(self, code, url, data={}):
@@ -251,10 +239,27 @@ class PageLoader(ActivateLoginTestCase):
"""
resp = self.client.post(url, data)
self.assertEqual(resp.status_code, code,
- "got code {0} for url '{1}'. Expected code {2}"
- .format(resp.status_code, url, code))
+ "got code {0} for url '{1}'. Expected code {2}".format(resp.status_code, url, code))
return resp
+
+class ActivateLoginTest(LoginEnrollmentTestCase):
+ '''Test logging in and logging out'''
+ def setUp(self):
+ self.setup_viewtest_user()
+
+ def test_activate_login(self):
+ '''Test login -- the setup function does all the work'''
+ pass
+
+ def test_logout(self):
+ '''Test logout -- setup function does login'''
+ self.logout()
+
+
+class PageLoaderTestCase(LoginEnrollmentTestCase):
+ ''' Base class that adds a function to load all pages in a modulestore '''
+
def check_pages_load(self, module_store):
"""Make all locations in course load"""
# enroll in the course before trying to access pages
@@ -264,14 +269,14 @@ class PageLoader(ActivateLoginTestCase):
self.enroll(course)
course_id = course.id
- n = 0
+ num = 0
num_bad = 0
all_ok = True
for descriptor in module_store.get_items(
Location(None, None, None, None, None)):
- n += 1
+ num += 1
print "Checking ", descriptor.location.url()
# We have ancillary course information now as modules and we can't simply use 'jump_to' to view them
@@ -332,45 +337,43 @@ class PageLoader(ActivateLoginTestCase):
print msg
self.assertTrue(all_ok) # fail fast
- print "{0}/{1} good".format(n - num_bad, n)
- log.info("{0}/{1} good".format(n - num_bad, n))
+ print "{0}/{1} good".format(num - num_bad, num)
+ log.info("{0}/{1} good".format(num - num_bad, num))
self.assertTrue(all_ok)
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
-class TestCoursesLoadTestCase_XmlModulestore(PageLoader):
- '''Check that all pages in test courses load properly'''
+class TestCoursesLoadTestCase_XmlModulestore(PageLoaderTestCase):
+ '''Check that all pages in test courses load properly from XML'''
def setUp(self):
- ActivateLoginTestCase.setUp(self)
+ self.setup_viewtest_user()
xmodule.modulestore.django._MODULESTORES = {}
def test_toy_course_loads(self):
- module_store = XMLModuleStore(
- TEST_DATA_DIR,
- default_class='xmodule.hidden_module.HiddenDescriptor',
- course_dirs=['toy'],
- load_error_modules=True,
+ module_store = XMLModuleStore(TEST_DATA_DIR,
+ default_class='xmodule.hidden_module.HiddenDescriptor',
+ course_dirs=['toy'],
+ load_error_modules=True,
)
self.check_pages_load(module_store)
def test_full_course_loads(self):
- module_store = XMLModuleStore(
- TEST_DATA_DIR,
- default_class='xmodule.hidden_module.HiddenDescriptor',
- course_dirs=['full'],
- load_error_modules=True,
+ module_store = XMLModuleStore(TEST_DATA_DIR,
+ default_class='xmodule.hidden_module.HiddenDescriptor',
+ course_dirs=['full'],
+ load_error_modules=True,
)
self.check_pages_load(module_store)
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
-class TestCoursesLoadTestCase_MongoModulestore(PageLoader):
- '''Check that all pages in test courses load properly'''
+class TestCoursesLoadTestCase_MongoModulestore(PageLoaderTestCase):
+ '''Check that all pages in test courses load properly from Mongo'''
def setUp(self):
- ActivateLoginTestCase.setUp(self)
+ self.setup_viewtest_user()
xmodule.modulestore.django._MODULESTORES = {}
modulestore().collection.drop()
@@ -386,7 +389,7 @@ class TestCoursesLoadTestCase_MongoModulestore(PageLoader):
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
-class TestNavigation(PageLoader):
+class TestNavigation(LoginEnrollmentTestCase):
"""Check that navigation state is saved properly"""
def setUp(self):
@@ -447,7 +450,7 @@ class TestDraftModuleStore(TestCase):
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
-class TestViewAuth(PageLoader):
+class TestViewAuth(LoginEnrollmentTestCase):
"""Check that view authentication works properly"""
# NOTE: setUpClass() runs before override_settings takes effect, so
@@ -492,7 +495,7 @@ class TestViewAuth(PageLoader):
'gradebook',
'grade_summary',)]
urls.append(reverse('student_progress', kwargs={'course_id': course.id,
- 'student_id': user(self.student).id}))
+ 'student_id': get_user(self.student).id}))
return urls
# shouldn't be able to get to the instructor pages
@@ -502,8 +505,8 @@ class TestViewAuth(PageLoader):
# Make the instructor staff in the toy course
group_name = _course_staff_group_name(self.toy.location)
- g = Group.objects.create(name=group_name)
- g.user_set.add(user(self.instructor))
+ group = Group.objects.create(name=group_name)
+ group.user_set.add(get_user(self.instructor))
self.logout()
self.login(self.instructor, self.password)
@@ -518,9 +521,9 @@ class TestViewAuth(PageLoader):
self.check_for_get_code(404, url)
# now also make the instructor staff
- u = user(self.instructor)
- u.is_staff = True
- u.save()
+ instructor = get_user(self.instructor)
+ instructor.is_staff = True
+ instructor.save()
# and now should be able to load both
for url in instructor_urls(self.toy) + instructor_urls(self.full):
@@ -627,7 +630,7 @@ class TestViewAuth(PageLoader):
# to make access checking smarter and understand both the effective
# user (the student), and the requesting user (the prof)
url = reverse('student_progress', kwargs={'course_id': course.id,
- 'student_id': user(self.student).id})
+ 'student_id': get_user(self.student).id})
print 'checking for 404 on view-as-student: {0}'.format(url)
self.check_for_get_code(404, url)
@@ -648,8 +651,8 @@ class TestViewAuth(PageLoader):
print '=== Testing course instructor access....'
# Make the instructor staff in the toy course
group_name = _course_staff_group_name(self.toy.location)
- g = Group.objects.create(name=group_name)
- g.user_set.add(user(self.instructor))
+ group = Group.objects.create(name=group_name)
+ group.user_set.add(get_user(self.instructor))
self.logout()
self.login(self.instructor, self.password)
@@ -663,9 +666,9 @@ class TestViewAuth(PageLoader):
print '=== Testing staff access....'
# now also make the instructor staff
- u = user(self.instructor)
- u.is_staff = True
- u.save()
+ instructor = get_user(self.instructor)
+ instructor.is_staff = True
+ instructor.save()
# and now should be able to load both
check_staff(self.toy)
@@ -698,8 +701,8 @@ class TestViewAuth(PageLoader):
print '=== Testing course instructor access....'
# Make the instructor staff in the toy course
group_name = _course_staff_group_name(self.toy.location)
- g = Group.objects.create(name=group_name)
- g.user_set.add(user(self.instructor))
+ group = Group.objects.create(name=group_name)
+ group.user_set.add(get_user(self.instructor))
print "logout/login"
self.logout()
@@ -709,10 +712,10 @@ class TestViewAuth(PageLoader):
print '=== Testing staff access....'
# now make the instructor global staff, but not in the instructor group
- g.user_set.remove(user(self.instructor))
- u = user(self.instructor)
- u.is_staff = True
- u.save()
+ group.user_set.remove(get_user(self.instructor))
+ instructor = get_user(self.instructor)
+ instructor.is_staff = True
+ instructor.save()
# unenroll and try again
self.unenroll(self.toy)
@@ -726,8 +729,8 @@ class TestViewAuth(PageLoader):
# Make courses start in the future
tomorrow = time.time() + 24 * 3600
- nextday = tomorrow + 24 * 3600
- yesterday = time.time() - 24 * 3600
+ # nextday = tomorrow + 24 * 3600
+ # yesterday = time.time() - 24 * 3600
# toy course's hasn't started
self.toy.lms.start = time.gmtime(tomorrow)
@@ -737,20 +740,20 @@ class TestViewAuth(PageLoader):
self.toy.lms.days_early_for_beta = 2
# student user shouldn't see it
- student_user = user(self.student)
+ student_user = get_user(self.student)
self.assertFalse(has_access(student_user, self.toy, 'load'))
# now add the student to the beta test group
group_name = course_beta_test_group_name(self.toy.location)
- g = Group.objects.create(name=group_name)
- g.user_set.add(student_user)
+ group = Group.objects.create(name=group_name)
+ group.user_set.add(student_user)
# now the student should see it
self.assertTrue(has_access(student_user, self.toy, 'load'))
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
-class TestCourseGrader(PageLoader):
+class TestCourseGrader(LoginEnrollmentTestCase):
"""Check that a course gets graded properly"""
# NOTE: setUpClass() runs before override_settings takes effect, so
@@ -773,35 +776,39 @@ class TestCourseGrader(PageLoader):
self.activate_user(self.student)
self.enroll(self.graded_course)
- self.student_user = user(self.student)
+ self.student_user = get_user(self.student)
self.factory = RequestFactory()
def get_grade_summary(self):
+ '''calls grades.grade for current user and course'''
model_data_cache = ModelDataCache.cache_for_descriptor_descendents(
self.graded_course.id, self.student_user, self.graded_course)
fake_request = self.factory.get(reverse('progress',
- kwargs={'course_id': self.graded_course.id}))
+ kwargs={'course_id': self.graded_course.id}))
return grades.grade(self.student_user, fake_request,
self.graded_course, model_data_cache)
def get_homework_scores(self):
+ '''get scores for homeworks'''
return self.get_grade_summary()['totaled_scores']['Homework']
def get_progress_summary(self):
+ '''return progress summary structure for current user and course'''
model_data_cache = ModelDataCache.cache_for_descriptor_descendents(
self.graded_course.id, self.student_user, self.graded_course)
fake_request = self.factory.get(reverse('progress',
- kwargs={'course_id': self.graded_course.id}))
+ kwargs={'course_id': self.graded_course.id}))
progress_summary = grades.progress_summary(self.student_user, fake_request,
self.graded_course, model_data_cache)
return progress_summary
def check_grade_percent(self, percent):
+ '''assert that percent grade is as expected'''
grade_summary = self.get_grade_summary()
self.assertEqual(grade_summary['percent'], percent)
@@ -816,10 +823,9 @@ class TestCourseGrader(PageLoader):
problem_location = "i4x://edX/graded/problem/{0}".format(problem_url_name)
modx_url = reverse('modx_dispatch',
- kwargs={
- 'course_id': self.graded_course.id,
- 'location': problem_location,
- 'dispatch': 'problem_check', })
+ kwargs={'course_id': self.graded_course.id,
+ 'location': problem_location,
+ 'dispatch': 'problem_check', })
resp = self.client.post(modx_url, {
'input_i4x-edX-graded-problem-{0}_2_1'.format(problem_url_name): responses[0],
@@ -831,16 +837,17 @@ class TestCourseGrader(PageLoader):
return resp
def problem_location(self, problem_url_name):
+ '''Get location string for problem, assuming hardcoded course_id'''
return "i4x://edX/graded/problem/{0}".format(problem_url_name)
def reset_question_answer(self, problem_url_name):
+ '''resets specified problem for current user'''
problem_location = self.problem_location(problem_url_name)
modx_url = reverse('modx_dispatch',
- kwargs={
- 'course_id': self.graded_course.id,
- 'location': problem_location,
- 'dispatch': 'problem_reset', })
+ kwargs={'course_id': self.graded_course.id,
+ 'location': problem_location,
+ 'dispatch': 'problem_reset', })
resp = self.client.post(modx_url)
return resp
@@ -855,6 +862,7 @@ class TestCourseGrader(PageLoader):
return [s.earned for s in self.get_homework_scores()]
def score_for_hw(hw_url_name):
+ """returns list of scores for a given url"""
hw_section = [section for section
in self.get_progress_summary()[0]['sections']
if section.get('url_name') == hw_url_name][0]
diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py
index e75ef8e8cf..9099d21233 100644
--- a/lms/djangoapps/courseware/views.py
+++ b/lms/djangoapps/courseware/views.py
@@ -522,6 +522,12 @@ def static_university_profile(request, org_id):
"""
Return the profile for the particular org_id that does not have any courses.
"""
+ # Redirect to the properly capitalized org_id
+ last_path = request.path.split('/')[-1]
+ if last_path != org_id:
+ return redirect('static_university_profile', org_id=org_id)
+
+ # Render template
template_file = "university_profile/{0}.html".format(org_id).lower()
context = dict(courses=[], org_id=org_id)
return render_to_response(template_file, context)
@@ -533,17 +539,28 @@ def university_profile(request, org_id):
"""
Return the profile for the particular org_id. 404 if it's not valid.
"""
+ virtual_orgs_ids = settings.VIRTUAL_UNIVERSITIES
+ meta_orgs = getattr(settings, 'META_UNIVERSITIES', {})
+
+ # Get all the ids associated with this organization
all_courses = modulestore().get_courses()
- valid_org_ids = set(c.org for c in all_courses).union(settings.VIRTUAL_UNIVERSITIES)
- if org_id not in valid_org_ids:
+ valid_orgs_ids = set(c.org for c in all_courses)
+ valid_orgs_ids.update(virtual_orgs_ids + meta_orgs.keys())
+
+ if org_id not in valid_orgs_ids:
raise Http404("University Profile not found for {0}".format(org_id))
- # Only grab courses for this org...
- courses = get_courses_by_university(request.user,
- domain=request.META.get('HTTP_HOST'))[org_id]
- courses = sort_by_announcement(courses)
+ # Grab all courses for this organization(s)
+ org_ids = set([org_id] + meta_orgs.get(org_id, []))
+ org_courses = []
+ domain = request.META.get('HTTP_HOST')
+ for key in org_ids:
+ cs = get_courses_by_university(request.user, domain=domain)[key]
+ org_courses.extend(cs)
- context = dict(courses=courses, org_id=org_id)
+ org_courses = sort_by_announcement(org_courses)
+
+ context = dict(courses=org_courses, org_id=org_id)
template_file = "university_profile/{0}.html".format(org_id).lower()
return render_to_response(template_file, context)
diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py
index 3eee0948da..3a517af26e 100644
--- a/lms/djangoapps/django_comment_client/forum/views.py
+++ b/lms/djangoapps/django_comment_client/forum/views.py
@@ -1,28 +1,22 @@
import json
import logging
+import xml.sax.saxutils as saxutils
from django.contrib.auth.decorators import login_required
-from django.views.decorators.http import require_POST
-from django.http import HttpResponse, Http404
-from django.utils import simplejson
+from django.http import Http404
from django.core.context_processors import csrf
-from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from mitxmako.shortcuts import render_to_response, render_to_string
from courseware.courses import get_course_with_access
-from course_groups.cohorts import is_course_cohorted, get_cohort_id, is_commentable_cohorted, get_cohorted_commentables, get_cohort, get_course_cohorts, get_cohort_by_id
+from course_groups.cohorts import (is_course_cohorted, get_cohort_id, is_commentable_cohorted,
+ get_cohorted_commentables, get_course_cohorts, get_cohort_by_id)
from courseware.access import has_access
-from urllib import urlencode
-from operator import methodcaller
-from django_comment_client.permissions import check_permissions_by_view, cached_has_permission
-from django_comment_client.utils import (merge_dict, extract, strip_none,
- strip_blank, get_courseware_context)
-
+from django_comment_client.permissions import cached_has_permission
+from django_comment_client.utils import (merge_dict, extract, strip_none, get_courseware_context)
import django_comment_client.utils as utils
import comment_client as cc
-import xml.sax.saxutils as saxutils
THREADS_PER_PAGE = 20
INLINE_THREADS_PER_PAGE = 20
@@ -31,6 +25,7 @@ escapedict = {'"': '"'}
log = logging.getLogger("edx.discussions")
+@login_required
def get_threads(request, course_id, discussion_id=None, per_page=THREADS_PER_PAGE):
"""
This may raise cc.utils.CommentClientError or
@@ -60,7 +55,6 @@ def get_threads(request, course_id, discussion_id=None, per_page=THREADS_PER_PAG
cc_user.default_sort_key = request.GET.get('sort_key')
cc_user.save()
-
#there are 2 dimensions to consider when executing a search with respect to group id
#is user a moderator
#did the user request a group
@@ -91,18 +85,17 @@ def get_threads(request, course_id, discussion_id=None, per_page=THREADS_PER_PAG
#now add the group name if the thread has a group id
for thread in threads:
-
+
if thread.get('group_id'):
thread['group_name'] = get_cohort_by_id(course_id, thread.get('group_id')).name
thread['group_string'] = "This post visible only to Group %s." % (thread['group_name'])
else:
thread['group_name'] = ""
thread['group_string'] = "This post visible to everyone."
-
+
#patch for backward compatibility to comments service
if not 'pinned' in thread:
thread['pinned'] = False
-
query_params['page'] = page
query_params['num_pages'] = num_pages
@@ -110,6 +103,7 @@ def get_threads(request, course_id, discussion_id=None, per_page=THREADS_PER_PAG
return threads, query_params
+@login_required
def inline_discussion(request, course_id, discussion_id):
"""
Renders JSON for DiscussionModules
@@ -142,14 +136,14 @@ def inline_discussion(request, course_id, discussion_id):
cohorts_list = list()
if is_cohorted:
- cohorts_list.append({'name':'All Groups','id':None})
+ cohorts_list.append({'name': 'All Groups', 'id': None})
#if you're a mod, send all cohorts and let you pick
if is_moderator:
cohorts = get_course_cohorts(course_id)
for c in cohorts:
- cohorts_list.append({'name':c.name, 'id':c.id})
+ cohorts_list.append({'name': c.name, 'id': c.id})
else:
#students don't get to choose
@@ -216,9 +210,6 @@ def forum_form_discussion(request, course_id):
user_cohort_id = get_cohort_id(request.user, course_id)
-
-
-
context = {
'csrf': csrf(request)['csrf_token'],
'course': course,
@@ -242,6 +233,7 @@ def forum_form_discussion(request, course_id):
return render_to_response('discussion/index.html', context)
+
@login_required
def single_thread(request, course_id, discussion_id, thread_id):
course = get_course_with_access(request.user, course_id, 'load')
@@ -250,11 +242,11 @@ def single_thread(request, course_id, discussion_id, thread_id):
try:
thread = cc.Thread.find(thread_id).retrieve(recursive=True, user_id=request.user.id)
-
+
#patch for backward compatibility with comments service
if not 'pinned' in thread.attributes:
thread['pinned'] = False
-
+
except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err:
log.error("Error loading single thread.")
raise Http404
@@ -352,7 +344,7 @@ def user_profile(request, course_id, user_id):
query_params = {
'page': request.GET.get('page', 1),
'per_page': THREADS_PER_PAGE, # more than threads_per_page to show more activities
- }
+ }
threads, page, num_pages = profiled_user.active_threads(query_params)
query_params['page'] = page
@@ -369,8 +361,6 @@ def user_profile(request, course_id, user_id):
'annotated_content_info': saxutils.escape(json.dumps(annotated_content_info), escapedict),
})
else:
-
-
context = {
'course': course,
'user': request.user,
@@ -426,5 +416,5 @@ def followed_threads(request, course_id, user_id):
}
return render_to_response('discussion/user_profile.html', context)
- except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError) as err:
+ except (cc.utils.CommentClientError, cc.utils.CommentClientUnknownError):
raise Http404
diff --git a/lms/djangoapps/django_comment_client/tests.py b/lms/djangoapps/django_comment_client/tests.py
index 1d925cdb8e..a35df54cd9 100644
--- a/lms/djangoapps/django_comment_client/tests.py
+++ b/lms/djangoapps/django_comment_client/tests.py
@@ -1,73 +1,12 @@
-from django.contrib.auth.models import User, Group
-from django.core.urlresolvers import reverse
-from django.test import TestCase
-from django.test.client import RequestFactory
-from django.conf import settings
-
-from mock import Mock
-
-from django.test.utils import override_settings
-
-import xmodule.modulestore.django
-
-from student.models import CourseEnrollment
-
-from django.db.models.signals import m2m_changed, pre_delete, pre_save, post_delete, post_save
-from django.dispatch.dispatcher import _make_id
import string
import random
-from .permissions import has_permission
-from .models import Role, Permission
-from xmodule.modulestore.django import modulestore
-from xmodule.modulestore import Location
-from xmodule.modulestore.xml_importer import import_from_xml
-from xmodule.modulestore.xml import XMLModuleStore
-
-import comment_client
-
-from courseware.tests.tests import PageLoader, TEST_DATA_XML_MODULESTORE
-
-#@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
-#class TestCohorting(PageLoader):
-# """Check that cohorting works properly"""
-#
-# def setUp(self):
-# xmodule.modulestore.django._MODULESTORES = {}
-#
-# # Assume courses are there
-# self.toy = modulestore().get_course("edX/toy/2012_Fall")
-#
-# # Create two accounts
-# self.student = 'view@test.com'
-# self.student2 = 'view2@test.com'
-# self.password = 'foo'
-# self.create_account('u1', self.student, self.password)
-# self.create_account('u2', self.student2, self.password)
-# self.activate_user(self.student)
-# self.activate_user(self.student2)
-#
-# def test_create_thread(self):
-# my_save = Mock()
-# comment_client.perform_request = my_save
-#
-# resp = self.client.post(
-# reverse('django_comment_client.base.views.create_thread',
-# kwargs={'course_id': 'edX/toy/2012_Fall',
-# 'commentable_id': 'General'}),
-# {'some': "some",
-# 'data': 'data'})
-# self.assertTrue(my_save.called)
-#
-# #self.assertEqual(resp.status_code, 200)
-# #self.assertEqual(my_save.something, "expected", "complaint if not true")
-#
-# self.toy.cohort_config = {"cohorted": True}
-#
-# # call the view again ...
-#
-# # assert that different things happened
+from django.contrib.auth.models import User
+from django.test import TestCase
+from student.models import CourseEnrollment
+from django_comment_client.permissions import has_permission
+from django_comment_client.models import Role
class PermissionsTestCase(TestCase):
diff --git a/lms/djangoapps/instructor/tests.py b/lms/djangoapps/instructor/tests.py
index b775aa158a..512e81e302 100644
--- a/lms/djangoapps/instructor/tests.py
+++ b/lms/djangoapps/instructor/tests.py
@@ -8,13 +8,6 @@ Notes for running by hand:
django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/instructor
"""
-import courseware.tests.tests as ct
-
-import json
-
-from nose import SkipTest
-from mock import patch, Mock
-
from django.test.utils import override_settings
# Need access to internal func to put users in the right group
@@ -26,13 +19,13 @@ from django_comment_client.models import Role, FORUM_ROLE_ADMINISTRATOR, \
from django_comment_client.utils import has_forum_access
from courseware.access import _course_staff_group_name
-import courseware.tests.tests as ct
+from courseware.tests.tests import LoginEnrollmentTestCase, TEST_DATA_XML_MODULESTORE, get_user
from xmodule.modulestore.django import modulestore
import xmodule.modulestore.django
-@override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE)
-class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader):
+@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
+class TestInstructorDashboardGradeDownloadCSV(LoginEnrollmentTestCase):
'''
Check for download of csv
'''
@@ -55,7 +48,7 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader):
def make_instructor(course):
group_name = _course_staff_group_name(course.location)
g = Group.objects.create(name=group_name)
- g.user_set.add(ct.user(self.instructor))
+ g.user_set.add(get_user(self.instructor))
make_instructor(self.toy)
@@ -63,7 +56,6 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader):
self.login(self.instructor, self.password)
self.enroll(self.toy)
-
def test_download_grades_csv(self):
course = self.toy
url = reverse('instructor_dashboard', kwargs={'course_id': course.id})
@@ -101,9 +93,8 @@ def action_name(operation, rolename):
return '{0} forum {1}'.format(operation, FORUM_ADMIN_ACTION_SUFFIX[rolename])
-
-@override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE)
-class TestInstructorDashboardForumAdmin(ct.PageLoader):
+@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
+class TestInstructorDashboardForumAdmin(LoginEnrollmentTestCase):
'''
Check for change in forum admin role memberships
'''
@@ -112,7 +103,6 @@ class TestInstructorDashboardForumAdmin(ct.PageLoader):
xmodule.modulestore.django._MODULESTORES = {}
courses = modulestore().get_courses()
-
self.course_id = "edX/toy/2012_Fall"
self.toy = modulestore().get_course(self.course_id)
@@ -127,14 +117,12 @@ class TestInstructorDashboardForumAdmin(ct.PageLoader):
group_name = _course_staff_group_name(self.toy.location)
g = Group.objects.create(name=group_name)
- g.user_set.add(ct.user(self.instructor))
+ g.user_set.add(get_user(self.instructor))
self.logout()
self.login(self.instructor, self.password)
self.enroll(self.toy)
-
-
def initialize_roles(self, course_id):
self.admin_role = Role.objects.get_or_create(name=FORUM_ROLE_ADMINISTRATOR, course_id=course_id)[0]
self.moderator_role = Role.objects.get_or_create(name=FORUM_ROLE_MODERATOR, course_id=course_id)[0]
diff --git a/lms/djangoapps/licenses/tests.py b/lms/djangoapps/licenses/tests.py
index 232c853b62..5289c31bc6 100644
--- a/lms/djangoapps/licenses/tests.py
+++ b/lms/djangoapps/licenses/tests.py
@@ -1,22 +1,138 @@
+"""Tests for License package"""
import logging
+import json
+
from uuid import uuid4
from random import shuffle
from tempfile import NamedTemporaryFile
+from factory import Factory, SubFactory
from django.test import TestCase
from django.core.management import call_command
-
-from .models import CourseSoftware, UserLicense
+from django.core.urlresolvers import reverse
+from licenses.models import CourseSoftware, UserLicense
+from courseware.tests.tests import LoginEnrollmentTestCase, get_user
COURSE_1 = 'edX/toy/2012_Fall'
SOFTWARE_1 = 'matlab'
SOFTWARE_2 = 'stata'
+SERIAL_1 = '123456abcde'
+
log = logging.getLogger(__name__)
+class CourseSoftwareFactory(Factory):
+ '''Factory for generating CourseSoftware objects in database'''
+ FACTORY_FOR = CourseSoftware
+
+ name = SOFTWARE_1
+ full_name = SOFTWARE_1
+ url = SOFTWARE_1
+ course_id = COURSE_1
+
+
+class UserLicenseFactory(Factory):
+ '''
+ Factory for generating UserLicense objects in database
+
+ By default, the user assigned is null, indicating that the
+ serial number has not yet been assigned.
+ '''
+ FACTORY_FOR = UserLicense
+
+ software = SubFactory(CourseSoftwareFactory)
+ serial = SERIAL_1
+
+
+class LicenseTestCase(LoginEnrollmentTestCase):
+ '''Tests for licenses.views'''
+ def setUp(self):
+ '''creates a user and logs in'''
+ self.setup_viewtest_user()
+ self.software = CourseSoftwareFactory()
+
+ def test_get_license(self):
+ UserLicenseFactory(user=get_user(self.viewtest_email), software=self.software)
+ response = self.client.post(reverse('user_software_license'),
+ {'software': SOFTWARE_1, 'generate': 'false'},
+ HTTP_X_REQUESTED_WITH='XMLHttpRequest',
+ HTTP_REFERER='/courses/{0}/some_page'.format(COURSE_1))
+ self.assertEqual(200, response.status_code)
+ json_returned = json.loads(response.content)
+ self.assertFalse('error' in json_returned)
+ self.assertTrue('serial' in json_returned)
+ self.assertEquals(json_returned['serial'], SERIAL_1)
+
+ def test_get_nonexistent_license(self):
+ response = self.client.post(reverse('user_software_license'),
+ {'software': SOFTWARE_1, 'generate': 'false'},
+ HTTP_X_REQUESTED_WITH='XMLHttpRequest',
+ HTTP_REFERER='/courses/{0}/some_page'.format(COURSE_1))
+ self.assertEqual(200, response.status_code)
+ json_returned = json.loads(response.content)
+ self.assertFalse('serial' in json_returned)
+ self.assertTrue('error' in json_returned)
+
+ def test_create_nonexistent_license(self):
+ '''Should not assign a license to an unlicensed user when none are available'''
+ response = self.client.post(reverse('user_software_license'),
+ {'software': SOFTWARE_1, 'generate': 'true'},
+ HTTP_X_REQUESTED_WITH='XMLHttpRequest',
+ HTTP_REFERER='/courses/{0}/some_page'.format(COURSE_1))
+ self.assertEqual(200, response.status_code)
+ json_returned = json.loads(response.content)
+ self.assertFalse('serial' in json_returned)
+ self.assertTrue('error' in json_returned)
+
+ def test_create_license(self):
+ '''Should assign a license to an unlicensed user if one is unassigned'''
+ # create an unassigned license
+ UserLicenseFactory(software=self.software)
+ response = self.client.post(reverse('user_software_license'),
+ {'software': SOFTWARE_1, 'generate': 'true'},
+ HTTP_X_REQUESTED_WITH='XMLHttpRequest',
+ HTTP_REFERER='/courses/{0}/some_page'.format(COURSE_1))
+ self.assertEqual(200, response.status_code)
+ json_returned = json.loads(response.content)
+ self.assertFalse('error' in json_returned)
+ self.assertTrue('serial' in json_returned)
+ self.assertEquals(json_returned['serial'], SERIAL_1)
+
+ def test_get_license_from_wrong_course(self):
+ response = self.client.post(reverse('user_software_license'),
+ {'software': SOFTWARE_1, 'generate': 'false'},
+ HTTP_X_REQUESTED_WITH='XMLHttpRequest',
+ HTTP_REFERER='/courses/{0}/some_page'.format('some/other/course'))
+ self.assertEqual(404, response.status_code)
+
+ def test_get_license_from_non_ajax(self):
+ response = self.client.post(reverse('user_software_license'),
+ {'software': SOFTWARE_1, 'generate': 'false'},
+ HTTP_REFERER='/courses/{0}/some_page'.format(COURSE_1))
+ self.assertEqual(404, response.status_code)
+
+ def test_get_license_without_software(self):
+ response = self.client.post(reverse('user_software_license'),
+ {'generate': 'false'},
+ HTTP_X_REQUESTED_WITH='XMLHttpRequest',
+ HTTP_REFERER='/courses/{0}/some_page'.format(COURSE_1))
+ self.assertEqual(404, response.status_code)
+
+ def test_get_license_without_login(self):
+ self.logout()
+ response = self.client.post(reverse('user_software_license'),
+ {'software': SOFTWARE_1, 'generate': 'false'},
+ HTTP_X_REQUESTED_WITH='XMLHttpRequest',
+ HTTP_REFERER='/courses/{0}/some_page'.format(COURSE_1))
+ # if we're not logged in, we should be referred to the login page
+ self.assertEqual(302, response.status_code)
+
+
class CommandTest(TestCase):
+ '''Test management command for importing serial numbers'''
+
def test_import_serial_numbers(self):
size = 20
@@ -51,31 +167,33 @@ class CommandTest(TestCase):
licenses_count = UserLicense.objects.all().count()
self.assertEqual(3 * size, licenses_count)
- cs = CourseSoftware.objects.get(pk=1)
+ software = CourseSoftware.objects.get(pk=1)
- lics = UserLicense.objects.filter(software=cs)[:size]
+ lics = UserLicense.objects.filter(software=software)[:size]
known_serials = list(l.serial for l in lics)
known_serials.extend(generate_serials(10))
shuffle(known_serials)
log.debug('Adding some new and old serials to {0}'.format(SOFTWARE_1))
- with NamedTemporaryFile() as f:
- f.write('\n'.join(known_serials))
- f.flush()
- args = [COURSE_1, SOFTWARE_1, f.name]
+ with NamedTemporaryFile() as tmpfile:
+ tmpfile.write('\n'.join(known_serials))
+ tmpfile.flush()
+ args = [COURSE_1, SOFTWARE_1, tmpfile.name]
call_command('import_serial_numbers', *args)
log.debug('Check if we added only the new ones')
- licenses_count = UserLicense.objects.filter(software=cs).count()
+ licenses_count = UserLicense.objects.filter(software=software).count()
self.assertEqual((2 * size) + 10, licenses_count)
def generate_serials(size=20):
+ '''generate a list of serial numbers'''
return [str(uuid4()) for _ in range(size)]
def generate_serials_file(size=20):
+ '''output list of generated serial numbers to a temp file'''
serials = generate_serials(size)
temp_file = NamedTemporaryFile()
diff --git a/lms/djangoapps/licenses/views.py b/lms/djangoapps/licenses/views.py
index 20966427ba..1c1a80ed31 100644
--- a/lms/djangoapps/licenses/views.py
+++ b/lms/djangoapps/licenses/views.py
@@ -7,12 +7,13 @@ from collections import namedtuple, defaultdict
from mitxmako.shortcuts import render_to_string
+from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.http import HttpResponse, Http404
-from django.views.decorators.csrf import requires_csrf_token, csrf_protect
+from django.views.decorators.csrf import requires_csrf_token
-from .models import CourseSoftware
-from .models import get_courses_licenses, get_or_create_license, get_license
+from licenses.models import CourseSoftware
+from licenses.models import get_courses_licenses, get_or_create_license, get_license
log = logging.getLogger("mitx.licenses")
@@ -44,6 +45,7 @@ def get_licenses_by_course(user, courses):
return data_by_course
+@login_required
@requires_csrf_token
def user_software_license(request):
if request.method != 'POST' or not request.is_ajax():
@@ -65,19 +67,21 @@ def user_software_license(request):
try:
software = CourseSoftware.objects.get(name=software_name,
course_id=course_id)
- print software
except CourseSoftware.DoesNotExist:
raise Http404
- user = User.objects.get(id=user_id)
+ try:
+ user = User.objects.get(id=user_id)
+ except User.DoesNotExist:
+ raise Http404
if generate:
- license = get_or_create_license(user, software)
+ software_license = get_or_create_license(user, software)
else:
- license = get_license(user, software)
+ software_license = get_license(user, software)
- if license:
- response = {'serial': license.serial}
+ if software_license:
+ response = {'serial': software_license.serial}
else:
response = {'error': 'No serial number found'}
diff --git a/lms/djangoapps/open_ended_grading/tests.py b/lms/djangoapps/open_ended_grading/tests.py
index 1fd871d0cd..e554fdf0e1 100644
--- a/lms/djangoapps/open_ended_grading/tests.py
+++ b/lms/djangoapps/open_ended_grading/tests.py
@@ -4,22 +4,22 @@ Tests for open ended grading interfaces
django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/open_ended_grading
"""
-from django.test import TestCase
-from open_ended_grading import staff_grading_service
-from xmodule.open_ended_grading_classes import peer_grading_service
-from xmodule import peer_grading_module
+import json
+from mock import MagicMock
+
from django.core.urlresolvers import reverse
from django.contrib.auth.models import Group
+from mitxmako.shortcuts import render_to_string
-from courseware.access import _course_staff_group_name
-import courseware.tests.tests as ct
+from xmodule.open_ended_grading_classes import peer_grading_service
+from xmodule import peer_grading_module
from xmodule.modulestore.django import modulestore
import xmodule.modulestore.django
-from nose import SkipTest
-from mock import patch, Mock, MagicMock
-import json
from xmodule.x_module import ModuleSystem
-from mitxmako.shortcuts import render_to_string
+
+from open_ended_grading import staff_grading_service
+from courseware.access import _course_staff_group_name
+from courseware.tests.tests import LoginEnrollmentTestCase, TEST_DATA_XML_MODULESTORE, get_user
import logging
@@ -30,8 +30,8 @@ from django.http import QueryDict
from xmodule.tests import test_util_open_ended
-@override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE)
-class TestStaffGradingService(ct.PageLoader):
+@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
+class TestStaffGradingService(LoginEnrollmentTestCase):
'''
Check that staff grading service proxy works. Basically just checking the
access control and error handling logic -- all the actual work is on the
@@ -56,7 +56,7 @@ class TestStaffGradingService(ct.PageLoader):
def make_instructor(course):
group_name = _course_staff_group_name(course.location)
g = Group.objects.create(name=group_name)
- g.user_set.add(ct.user(self.instructor))
+ g.user_set.add(get_user(self.instructor))
make_instructor(self.toy)
@@ -126,8 +126,8 @@ class TestStaffGradingService(ct.PageLoader):
self.assertIsNotNone(d['problem_list'])
-@override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE)
-class TestPeerGradingService(ct.PageLoader):
+@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
+class TestPeerGradingService(LoginEnrollmentTestCase):
'''
Check that staff grading service proxy works. Basically just checking the
access control and error handling logic -- all the actual work is on the
diff --git a/lms/envs/aws.py b/lms/envs/aws.py
index cc9247b876..aa30315eca 100644
--- a/lms/envs/aws.py
+++ b/lms/envs/aws.py
@@ -76,6 +76,7 @@ LOGGING = get_logger_config(LOG_DIR,
COURSE_LISTINGS = ENV_TOKENS.get('COURSE_LISTINGS', {})
SUBDOMAIN_BRANDING = ENV_TOKENS.get('SUBDOMAIN_BRANDING', {})
VIRTUAL_UNIVERSITIES = ENV_TOKENS.get('VIRTUAL_UNIVERSITIES', [])
+META_UNIVERSITIES = ENV_TOKENS.get('META_UNIVERSITIES', {})
COMMENTS_SERVICE_URL = ENV_TOKENS.get("COMMENTS_SERVICE_URL", '')
COMMENTS_SERVICE_KEY = ENV_TOKENS.get("COMMENTS_SERVICE_KEY", '')
CERT_QUEUE = ENV_TOKENS.get("CERT_QUEUE", 'test-pull')
diff --git a/lms/envs/cms/dev.py b/lms/envs/cms/dev.py
index 4b6b0a12f0..9333b7883c 100644
--- a/lms/envs/cms/dev.py
+++ b/lms/envs/cms/dev.py
@@ -9,6 +9,7 @@ MITX_FEATURES['AUTH_USE_MIT_CERTIFICATES'] = False
SUBDOMAIN_BRANDING['edge'] = 'edge'
SUBDOMAIN_BRANDING['preview.edge'] = 'edge'
VIRTUAL_UNIVERSITIES = ['edge']
+META_UNIVERSITIES = {}
modulestore_options = {
'default_class': 'xmodule.raw_module.RawDescriptor',
diff --git a/lms/envs/common.py b/lms/envs/common.py
index cfd6fc34de..8654b5ebf5 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -364,6 +364,7 @@ TEMPLATE_LOADERS = (
MIDDLEWARE_CLASSES = (
'contentserver.middleware.StaticContentServer',
+ 'request_cache.middleware.RequestCache',
'django_comment_client.middleware.AjaxExceptionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
diff --git a/lms/envs/dev.py b/lms/envs/dev.py
index f204dc287b..24bad58459 100644
--- a/lms/envs/dev.py
+++ b/lms/envs/dev.py
@@ -113,6 +113,9 @@ SUBDOMAIN_BRANDING = {
# have an actual course with that org set
VIRTUAL_UNIVERSITIES = []
+# Organization that contain other organizations
+META_UNIVERSITIES = {'UTx': ['UTAustinX']}
+
COMMENTS_SERVICE_KEY = "PUT_YOUR_API_KEY_HERE"
############################## Course static files ##########################
diff --git a/lms/one_time_startup.py b/lms/one_time_startup.py
index 6b3c45d60f..e1b1f79444 100644
--- a/lms/one_time_startup.py
+++ b/lms/one_time_startup.py
@@ -2,13 +2,15 @@ import logging
from dogapi import dog_http_api, dog_stats_api
from django.conf import settings
from xmodule.modulestore.django import modulestore
+from request_cache.middleware import RequestCache
from django.core.cache import get_cache, InvalidCacheBackendError
cache = get_cache('mongo_metadata_inheritance')
for store_name in settings.MODULESTORE:
store = modulestore(store_name)
- store.metadata_inheritance_cache = cache
+ store.metadata_inheritance_cache_subsystem = cache
+ store.request_cache = RequestCache.get_request_cache()
if hasattr(settings, 'DATADOG_API'):
dog_http_api.api_key = settings.DATADOG_API
diff --git a/lms/static/images/university/utaustin/utaustin-cover_2025x550.jpg b/lms/static/images/university/utaustin/utaustin-cover_2025x550.jpg
new file mode 100644
index 0000000000..7294b53f1b
Binary files /dev/null and b/lms/static/images/university/utaustin/utaustin-cover_2025x550.jpg differ
diff --git a/lms/static/images/university/utaustin/utaustin-standalone_187x80.png b/lms/static/images/university/utaustin/utaustin-standalone_187x80.png
new file mode 100644
index 0000000000..5349800763
Binary files /dev/null and b/lms/static/images/university/utaustin/utaustin-standalone_187x80.png differ
diff --git a/lms/templates/university_profile/utaustinx.html b/lms/templates/university_profile/utaustinx.html
new file mode 100644
index 0000000000..b4f6b4446e
--- /dev/null
+++ b/lms/templates/university_profile/utaustinx.html
@@ -0,0 +1,23 @@
+<%inherit file="base.html" />
+<%namespace name='static' file='../static_content.html'/>
+
+<%block name="title">
UTAustinX%block>
+
+<%block name="university_header">
+
+
+
+
+
})
+
+ UTAustinx
+
+
+
+%block>
+
+<%block name="university_description">
+
The University of Texas at Austin is the top-ranked public university in a nearly 1,000-mile radius, and is ranked in the top 25 universities in the world. Students have been finding their passion in life at UT Austin for more than 130 years, and it has been a member of the prestigious AAU since 1929. UT Austin combines the academic depth and breadth of a world research institute (regularly ranking within the top three producers of doctoral degrees in the country) with the fun and excitement of a big-time collegiate experience. It is currently the fifth-largest university in America, with more than 50,000 students and 3,000 professors across 17 colleges and schools, and is the first major American university to build a medical school in the past 50 years.
+%block>
+
+${parent.body()}
diff --git a/lms/templates/university_profile/utx.html b/lms/templates/university_profile/utx.html
index b9378f6ce3..ea34ddb85b 100644
--- a/lms/templates/university_profile/utx.html
+++ b/lms/templates/university_profile/utx.html
@@ -1,5 +1,8 @@
<%inherit file="base.html" />
<%namespace name='static' file='../static_content.html'/>
+<%!
+ from django.core.urlresolvers import reverse
+%>
<%block name="title">
UTx%block>
@@ -19,6 +22,7 @@
<%block name="university_description">
Educating students, providing care for patients, conducting groundbreaking research and serving the needs of Texans and the nation for more than 130 years, The University of Texas System is one of the largest public university systems in the United States, with nine academic universities and six health science centers. Student enrollment exceeded 215,000 in the 2011 academic year. The UT System confers more than one-third of the state’s undergraduate degrees and educates nearly three-fourths of the state’s health care professionals annually. The UT System has an annual operating budget of $13.1 billion (FY 2012) including $2.3 billion in sponsored programs funded by federal, state, local and private sources. With roughly 87,000 employees, the UT System is one of the largest employers in the state.
+
Find out about the University of Texas Austin.
%block>
${parent.body()}
diff --git a/lms/urls.py b/lms/urls.py
index ee213f2b8c..de5c8184fa 100644
--- a/lms/urls.py
+++ b/lms/urls.py
@@ -69,44 +69,22 @@ urlpatterns = ('',
url(r'^heartbeat$', include('heartbeat.urls')),
- url(r'^university_profile/UTx$', 'courseware.views.static_university_profile',
- name="static_university_profile", kwargs={'org_id': 'UTx'}),
- url(r'^university_profile/WellesleyX$', 'courseware.views.static_university_profile',
+ url(r'^(?i)university_profile/WellesleyX$', 'courseware.views.static_university_profile',
name="static_university_profile", kwargs={'org_id': 'WellesleyX'}),
- url(r'^university_profile/GeorgetownX$', 'courseware.views.static_university_profile',
+ url(r'^(?i)university_profile/GeorgetownX$', 'courseware.views.static_university_profile',
name="static_university_profile", kwargs={'org_id': 'GeorgetownX'}),
-
- # Dan accidentally sent out a press release with lower case urls for McGill, Toronto,
- # Rice, ANU, Delft, and EPFL. Hence the redirects.
- url(r'^university_profile/McGillX$', 'courseware.views.static_university_profile',
+ url(r'^(?i)university_profile/McGillX$', 'courseware.views.static_university_profile',
name="static_university_profile", kwargs={'org_id': 'McGillX'}),
- url(r'^university_profile/mcgillx$',
- RedirectView.as_view(url='/university_profile/McGillX')),
-
- url(r'^university_profile/TorontoX$', 'courseware.views.static_university_profile',
+ url(r'^(?i)university_profile/TorontoX$', 'courseware.views.static_university_profile',
name="static_university_profile", kwargs={'org_id': 'TorontoX'}),
- url(r'^university_profile/torontox$',
- RedirectView.as_view(url='/university_profile/TorontoX')),
-
- url(r'^university_profile/RiceX$', 'courseware.views.static_university_profile',
+ url(r'^(?i)university_profile/RiceX$', 'courseware.views.static_university_profile',
name="static_university_profile", kwargs={'org_id': 'RiceX'}),
- url(r'^university_profile/ricex$',
- RedirectView.as_view(url='/university_profile/RiceX')),
-
- url(r'^university_profile/ANUx$', 'courseware.views.static_university_profile',
+ url(r'^(?i)university_profile/ANUx$', 'courseware.views.static_university_profile',
name="static_university_profile", kwargs={'org_id': 'ANUx'}),
- url(r'^university_profile/anux$',
- RedirectView.as_view(url='/university_profile/ANUx')),
-
- url(r'^university_profile/DelftX$', 'courseware.views.static_university_profile',
+ url(r'^(?i)university_profile/DelftX$', 'courseware.views.static_university_profile',
name="static_university_profile", kwargs={'org_id': 'DelftX'}),
- url(r'^university_profile/delftx$',
- RedirectView.as_view(url='/university_profile/DelftX')),
-
- url(r'^university_profile/EPFLx$', 'courseware.views.static_university_profile',
+ url(r'^(?i)university_profile/EPFLx$', 'courseware.views.static_university_profile',
name="static_university_profile", kwargs={'org_id': 'EPFLx'}),
- url(r'^university_profile/epflx$',
- RedirectView.as_view(url='/university_profile/EPFLx')),
url(r'^university_profile/(?P
[^/]+)$', 'courseware.views.university_profile',
name="university_profile"),