diff --git a/.gitignore b/.gitignore
index 493df5a7fd..b13a128a63 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,4 +28,5 @@ nosetests.xml
cover_html/
.idea/
.redcar/
-chromedriver.log
\ No newline at end of file
+chromedriver.log
+ghostdriver.log
diff --git a/cms/djangoapps/contentstore/features/advanced-settings.feature b/cms/djangoapps/contentstore/features/advanced-settings.feature
index 4708a60be1..779d44e4b2 100644
--- a/cms/djangoapps/contentstore/features/advanced-settings.feature
+++ b/cms/djangoapps/contentstore/features/advanced-settings.feature
@@ -7,6 +7,7 @@ Feature: Advanced (manual) course policy
When I select the Advanced Settings
Then I see only the display name
+ @skip-phantom
Scenario: Test if there are no policy settings without existing UI controls
Given I am on the Advanced Course Settings page in Studio
When I delete the display name
@@ -14,6 +15,7 @@ Feature: Advanced (manual) course policy
And I reload the page
Then there are no advanced policy settings
+ @skip-phantom
Scenario: Test cancel editing key name
Given I am on the Advanced Course Settings page in Studio
When I edit the name of a policy key
@@ -32,6 +34,7 @@ Feature: Advanced (manual) course policy
And I press the "Cancel" notification button
Then the policy key value is unchanged
+ @skip-phantom
Scenario: Test editing key value
Given I am on the Advanced Course Settings page in Studio
When I edit the value of a policy key
diff --git a/cms/djangoapps/contentstore/features/advanced-settings.py b/cms/djangoapps/contentstore/features/advanced-settings.py
index 91daf70718..ec58aeccc6 100644
--- a/cms/djangoapps/contentstore/features/advanced-settings.py
+++ b/cms/djangoapps/contentstore/features/advanced-settings.py
@@ -1,9 +1,10 @@
from lettuce import world, step
from common import *
import time
+from selenium.common.exceptions import WebDriverException
+from selenium.webdriver.support import expected_conditions as EC
-from nose.tools import assert_equal
-from nose.tools import assert_true
+from nose.tools import assert_true, assert_false, assert_equal
"""
http://selenium.googlecode.com/svn/trunk/docs/api/py/webdriver/selenium.webdriver.common.keys.html
@@ -19,6 +20,7 @@ def i_select_advanced_settings(step):
css_click(expand_icon_css)
link_css = 'li.nav-course-settings-advanced a'
css_click(link_css)
+ # world.browser.click_link_by_text('Advanced Settings')
@step('I am on the Advanced Course Settings page in Studio$')
@@ -42,8 +44,20 @@ def edit_the_name_of_a_policy_key(step):
@step(u'I press the "([^"]*)" notification button$')
def press_the_notification_button(step, name):
- world.browser.click_link_by_text(name)
+ def is_visible(driver):
+ return EC.visibility_of_element_located((By.CSS_SELECTOR,css,))
+ def is_invisible(driver):
+ return EC.invisibility_of_element_located((By.CSS_SELECTOR,css,))
+ css = 'a.%s-button' % name.lower()
+ wait_for(is_visible)
+
+ try:
+ css_click_at(css)
+ wait_for(is_invisible)
+ except WebDriverException, e:
+ css_click_at(css)
+ wait_for(is_invisible)
@step(u'I edit the value of a policy key$')
def edit_the_value_of_a_policy_key(step):
@@ -99,29 +113,29 @@ def it_is_formatted(step):
@step(u'the policy key name is unchanged$')
def the_policy_key_name_is_unchanged(step):
policy_key_css = 'input.policy-key'
- e = css_find(policy_key_css).first
- assert_equal(e.value, 'display_name')
+ val = css_find(policy_key_css).first.value
+ assert_equal(val, 'display_name')
@step(u'the policy key name is changed$')
def the_policy_key_name_is_changed(step):
policy_key_css = 'input.policy-key'
- e = css_find(policy_key_css).first
- assert_equal(e.value, 'new')
+ val = css_find(policy_key_css).first.value
+ assert_equal(val, 'new')
@step(u'the policy key value is unchanged$')
def the_policy_key_value_is_unchanged(step):
policy_value_css = 'li.course-advanced-policy-list-item div.value textarea'
- e = css_find(policy_value_css).first
- assert_equal(e.value, '"Robot Super Course"')
+ val = css_find(policy_value_css).first.value
+ assert_equal(val, '"Robot Super Course"')
@step(u'the policy key value is changed$')
def the_policy_key_value_is_unchanged(step):
policy_value_css = 'li.course-advanced-policy-list-item div.value textarea'
- e = css_find(policy_value_css).first
- assert_equal(e.value, '"Robot Super Course X"')
+ val = css_find(policy_value_css).first.value
+ assert_equal(val, '"Robot Super Course X"')
############# HELPERS ###############
@@ -132,19 +146,23 @@ def create_entry(key, value):
new_key_css = 'div#__new_advanced_key__ input'
new_key_element = css_find(new_key_css).first
new_key_element.fill(key)
-# For some reason have to get the instance for each command (get error that it is no longer attached to the DOM)
-# Have to do all this because Selenium has a bug that fill does not remove existing text
+# For some reason have to get the instance for each command
+# (get error that it is no longer attached to the DOM)
+# Have to do all this because Selenium fill does not remove existing text
new_value_css = 'div.CodeMirror textarea'
css_find(new_value_css).last.fill("")
css_find(new_value_css).last._element.send_keys(Keys.DELETE, Keys.DELETE)
css_find(new_value_css).last.fill(value)
+ # Add in a TAB key press because intermittently on ubuntu the
+ # last character of "value" above was not getting typed in
+ css_find(new_value_css).last._element.send_keys(Keys.TAB)
def delete_entry(index):
"""
Delete the nth entry where index is 0-based
"""
- css = '.delete-button'
+ css = 'a.delete-button'
assert_true(world.browser.is_element_present_by_css(css, 5))
delete_buttons = css_find(css)
assert_true(len(delete_buttons) > index, "no delete button exists for entry " + str(index))
@@ -165,16 +183,16 @@ def assert_entries(css, expected_values):
def click_save():
- css = ".save-button"
+ css = "a.save-button"
- def is_shown(driver):
- visible = css_find(css).first.visible
- if visible:
- # Even when waiting for visible, this fails sporadically. Adding in a small wait.
- time.sleep(float(1))
- return visible
- wait_for(is_shown)
- css_click(css)
+ # def is_shown(driver):
+ # visible = css_find(css).first.visible
+ # if visible:
+ # # Even when waiting for visible, this fails sporadically. Adding in a small wait.
+ # time.sleep(float(1))
+ # return visible
+ # wait_for(is_shown)
+ css_click_at(css)
def fill_last_field(value):
diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py
index 61b4fee9f6..f7e76ecf7f 100644
--- a/cms/djangoapps/contentstore/features/common.py
+++ b/cms/djangoapps/contentstore/features/common.py
@@ -3,18 +3,20 @@ from lettuce.django import django_url
from nose.tools import assert_true
from nose.tools import assert_equal
from selenium.webdriver.support.ui import WebDriverWait
+from selenium.common.exceptions import WebDriverException, StaleElementReferenceException
+from selenium.webdriver.support import expected_conditions as EC
+from selenium.webdriver.common.by import By
from terrain.factories import UserFactory, RegistrationFactory, UserProfileFactory
from terrain.factories import CourseFactory, GroupFactory
-import xmodule.modulestore.django
+from xmodule.modulestore.django import _MODULESTORES, modulestore
+from xmodule.templates import update_templates
from auth.authz import get_user_by_email
from logging import getLogger
logger = getLogger(__name__)
########### STEP HELPERS ##############
-
-
@step('I (?:visit|access|open) the Studio homepage$')
def i_visit_the_studio_homepage(step):
# To make this go to port 8001, put
@@ -52,9 +54,8 @@ def i_have_opened_a_new_course(step):
log_into_studio()
create_a_course()
+
####### HELPER FUNCTIONS ##############
-
-
def create_studio_user(
uname='robot',
email='robot+studio@edx.org',
@@ -83,9 +84,9 @@ def flush_xmodule_store():
# (though it shouldn't), do this manually
# from the bash shell to drop it:
# $ mongo test_xmodule --eval "db.dropDatabase()"
- xmodule.modulestore.django._MODULESTORES = {}
- xmodule.modulestore.django.modulestore().collection.drop()
- xmodule.templates.update_templates()
+ _MODULESTORES = {}
+ modulestore().collection.drop()
+ update_templates()
def assert_css_with_text(css, text):
@@ -94,8 +95,16 @@ def assert_css_with_text(css, text):
def css_click(css):
- assert_true(world.browser.is_element_present_by_css(css, 5))
- world.browser.find_by_css(css).first.click()
+ '''
+ 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:
+ css_find(css).first.click()
+ except WebDriverException, e:
+ css_click_at(css)
def css_click_at(css, x=10, y=10):
@@ -103,8 +112,7 @@ 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
'''
- assert_true(world.browser.is_element_present_by_css(css, 5))
- e = world.browser.find_by_css(css).first
+ 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()
@@ -115,11 +123,16 @@ def css_fill(css, value):
def css_find(css):
+ def is_visible(driver):
+ return EC.visibility_of_element_located((By.CSS_SELECTOR,css,))
+
+ assert_true(world.browser.is_element_present_by_css(css, 5))
+ wait_for(is_visible)
return world.browser.find_by_css(css)
def wait_for(func):
- WebDriverWait(world.browser.driver, 10).until(func)
+ WebDriverWait(world.browser.driver, 5).until(func)
def id_find(id):
diff --git a/cms/djangoapps/contentstore/features/section.feature b/cms/djangoapps/contentstore/features/section.feature
index 75e7a4af10..08d38367bc 100644
--- a/cms/djangoapps/contentstore/features/section.feature
+++ b/cms/djangoapps/contentstore/features/section.feature
@@ -26,9 +26,10 @@ Feature: Create Section
And I save a new section release date
Then the section release date is updated
+ @skip-phantom
Scenario: Delete section
Given I have opened a new course in Studio
And I have added a new section
When I press the "section" delete icon
And I confirm the alert
- Then the section does not exist
\ No newline at end of file
+ Then the section does not exist
diff --git a/cms/djangoapps/contentstore/features/section.py b/cms/djangoapps/contentstore/features/section.py
index cfa4e4bb52..b5ddb48a09 100644
--- a/cms/djangoapps/contentstore/features/section.py
+++ b/cms/djangoapps/contentstore/features/section.py
@@ -1,6 +1,8 @@
from lettuce import world, step
from common import *
from nose.tools import assert_equal
+from selenium.webdriver.common.keys import Keys
+import time
############### ACTIONS ####################
@@ -37,10 +39,14 @@ def i_save_a_new_section_release_date(step):
date_css = 'input.start-date.date.hasDatepicker'
time_css = 'input.start-time.time.ui-timepicker-input'
css_fill(date_css, '12/25/2013')
- # click here to make the calendar go away
- css_click(time_css)
+ # hit TAB to get to the time field
+ e = css_find(date_css).first
+ e._element.send_keys(Keys.TAB)
css_fill(time_css, '12:00am')
- css_click('a.save-button')
+ e = css_find(time_css).first
+ e._element.send_keys(Keys.TAB)
+ time.sleep(float(1))
+ world.browser.click_link_by_text('Save')
############ ASSERTIONS ###################
@@ -106,7 +112,7 @@ def the_section_release_date_picker_not_visible(step):
def the_section_release_date_is_updated(step):
css = 'span.published-status'
status_text = world.browser.find_by_css(css).text
- assert status_text == 'Will Release: 12/25/2013 at 12:00am'
+ assert_equal(status_text,'Will Release: 12/25/2013 at 12:00am')
############ HELPER METHODS ###################
@@ -120,4 +126,4 @@ def save_section_name(name):
def see_my_section_on_the_courseware_page(name):
section_css = 'span.section-name-span'
- assert_css_with_text(section_css, name)
\ No newline at end of file
+ assert_css_with_text(section_css, name)
diff --git a/cms/djangoapps/contentstore/features/signup.py b/cms/djangoapps/contentstore/features/signup.py
index a786225ead..e8d0dd8229 100644
--- a/cms/djangoapps/contentstore/features/signup.py
+++ b/cms/djangoapps/contentstore/features/signup.py
@@ -1,4 +1,5 @@
from lettuce import world, step
+from common import *
@step('I fill in the registration form$')
@@ -13,10 +14,11 @@ def i_fill_in_the_registration_form(step):
@step('I press the Create My Account button on the registration form$')
def i_press_the_button_on_the_registration_form(step):
- register_form = world.browser.find_by_css('form#register_form')
- submit_css = 'button#submit'
- register_form.find_by_css(submit_css).click()
-
+ submit_css = 'form#register_form button#submit'
+ # Workaround for click not working on ubuntu
+ # for some unknown reason.
+ e = css_find(submit_css)
+ e.type(' ')
@step('I should see be on the studio home page$')
def i_should_see_be_on_the_studio_home_page(step):
diff --git a/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature b/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature
index 5276b90d12..52c10e41a8 100644
--- a/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature
+++ b/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature
@@ -21,6 +21,7 @@ Feature: Overview Toggle Section
Then I see the "Collapse All Sections" link
And all sections are expanded
+ @skip-phantom
Scenario: Collapse link is not removed after last section of a course is deleted
Given I have a course with 1 section
And I navigate to the course overview page
diff --git a/cms/djangoapps/contentstore/features/subsection.feature b/cms/djangoapps/contentstore/features/subsection.feature
index 4b5f5b869d..1be5f4aeb9 100644
--- a/cms/djangoapps/contentstore/features/subsection.feature
+++ b/cms/djangoapps/contentstore/features/subsection.feature
@@ -17,6 +17,7 @@ Feature: Create Subsection
And I click to edit the subsection name
Then I see the complete subsection name with a quote in the editor
+ @skip-phantom
Scenario: Delete a subsection
Given I have opened a new course section in Studio
And I have added a new subsection
diff --git a/common/djangoapps/static_replace/__init__.py b/common/djangoapps/static_replace/__init__.py
index fb1f48d143..b73a658c5f 100644
--- a/common/djangoapps/static_replace/__init__.py
+++ b/common/djangoapps/static_replace/__init__.py
@@ -84,12 +84,19 @@ def replace_static_urls(text, data_directory, course_namespace=None):
if rest.endswith('?raw'):
return original
- # course_namespace is not None, then use studio style urls
- if course_namespace is not None and not isinstance(modulestore(), XMLModuleStore):
- url = StaticContent.convert_legacy_static_url(rest, course_namespace)
# In debug mode, if we can find the url as is,
- elif settings.DEBUG and finders.find(rest, True):
+ if settings.DEBUG and finders.find(rest, True):
return original
+ # if we're running with a MongoBacked store course_namespace is not None, then use studio style urls
+ elif course_namespace is not None and not isinstance(modulestore(), XMLModuleStore):
+ # first look in the static file pipeline and see if we are trying to reference
+ # a piece of static content which is in the mitx repo (e.g. JS associated with an xmodule)
+ if staticfiles_storage.exists(rest):
+ url = staticfiles_storage.url(rest)
+ else:
+ # if not, then assume it's courseware specific content and then look in the
+ # Mongo-backed database
+ url = StaticContent.convert_legacy_static_url(rest, course_namespace)
# Otherwise, look the file up in staticfiles_storage, and append the data directory if needed
else:
course_path = "/".join((data_directory, rest))
diff --git a/common/djangoapps/terrain/browser.py b/common/djangoapps/terrain/browser.py
index 8c2a8ba7a5..0881d86124 100644
--- a/common/djangoapps/terrain/browser.py
+++ b/common/djangoapps/terrain/browser.py
@@ -13,6 +13,7 @@ from django.core.management import call_command
def initial_setup(server):
# Launch the browser app (choose one of these below)
world.browser = Browser('chrome')
+ # world.browser = Browser('phantomjs')
# world.browser = Browser('firefox')
diff --git a/common/lib/capa/capa/capa_problem.py b/common/lib/capa/capa/capa_problem.py
index 14c590a660..8b32686985 100644
--- a/common/lib/capa/capa/capa_problem.py
+++ b/common/lib/capa/capa/capa_problem.py
@@ -29,6 +29,7 @@ import sys
from lxml import etree
from xml.sax.saxutils import unescape
+from copy import deepcopy
import chem
import chem.chemcalc
@@ -497,11 +498,10 @@ class LoncapaProblem(object):
Used by get_html.
'''
-
if (problemtree.tag == 'script' and problemtree.get('type')
and 'javascript' in problemtree.get('type')):
# leave javascript intact.
- return problemtree
+ return deepcopy(problemtree)
if problemtree.tag in html_problem_semantics:
return
diff --git a/common/lib/capa/capa/correctmap.py b/common/lib/capa/capa/correctmap.py
index f246b406d5..ea56863a2f 100644
--- a/common/lib/capa/capa/correctmap.py
+++ b/common/lib/capa/capa/correctmap.py
@@ -111,15 +111,14 @@ class CorrectMap(object):
return None
def get_npoints(self, answer_id):
- """ Return the number of points for an answer:
- If the answer is correct, return the assigned
- number of points (default: 1 point)
- Otherwise, return 0 points """
- if self.is_correct(answer_id):
- npoints = self.get_property(answer_id, 'npoints')
- return npoints if npoints is not None else 1
- else:
- return 0
+ """Return the number of points for an answer, used for partial credit."""
+ npoints = self.get_property(answer_id, 'npoints')
+ if npoints is not None:
+ return npoints
+ elif self.is_correct(answer_id):
+ return 1
+ # if not correct and no points have been assigned, return 0
+ return 0
def set_property(self, answer_id, property, value):
if answer_id in self.cmap:
diff --git a/common/lib/capa/capa/tests/test_correctmap.py b/common/lib/capa/capa/tests/test_correctmap.py
index 23734467bb..270ba4d849 100644
--- a/common/lib/capa/capa/tests/test_correctmap.py
+++ b/common/lib/capa/capa/tests/test_correctmap.py
@@ -91,12 +91,12 @@ class CorrectMapTest(unittest.TestCase):
npoints=0)
# Assert that we get the expected points
- # If points assigned and correct --> npoints
+ # If points assigned --> npoints
# If no points assigned and correct --> 1 point
- # Otherwise --> 0 points
+ # If no points assigned and incorrect --> 0 points
self.assertEqual(self.cmap.get_npoints('1_2_1'), 5)
self.assertEqual(self.cmap.get_npoints('2_2_1'), 1)
- self.assertEqual(self.cmap.get_npoints('3_2_1'), 0)
+ self.assertEqual(self.cmap.get_npoints('3_2_1'), 5)
self.assertEqual(self.cmap.get_npoints('4_2_1'), 0)
self.assertEqual(self.cmap.get_npoints('5_2_1'), 0)
diff --git a/common/lib/capa/capa/tests/test_html_render.py b/common/lib/capa/capa/tests/test_html_render.py
index ca2a3c2e2c..6c74d06ef4 100644
--- a/common/lib/capa/capa/tests/test_html_render.py
+++ b/common/lib/capa/capa/tests/test_html_render.py
@@ -3,6 +3,7 @@ from lxml import etree
import os
import textwrap
import json
+
import mock
from capa.capa_problem import LoncapaProblem
@@ -49,6 +50,8 @@ class CapaHtmlRenderTest(unittest.TestCase):
self.assertEqual(test_element.text, "Test include")
+
+
def test_process_outtext(self):
# Generate some XML with and
xml_str = textwrap.dedent("""
@@ -86,6 +89,25 @@ class CapaHtmlRenderTest(unittest.TestCase):
script_element = rendered_html.find('script')
self.assertEqual(None, script_element)
+ def test_render_javascript(self):
+ # Generate some XML with a
+
+ """)
+
+ # Create the problem
+ problem = LoncapaProblem(xml_str, '1', system=test_system)
+
+ # Render the HTML
+ rendered_html = etree.XML(problem.get_html())
+
+
+ # expect the javascript is still present in the rendered html
+ self.assertTrue("" in etree.tostring(rendered_html))
+
+
def test_render_response_xml(self):
# Generate some XML for a string response
kwargs = {'question_text': "Test question",
diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py
index b0d3950f06..2597690572 100644
--- a/common/lib/xmodule/xmodule/capa_module.py
+++ b/common/lib/xmodule/xmodule/capa_module.py
@@ -582,7 +582,7 @@ class CapaModule(XModule):
@staticmethod
def make_dict_of_responses(get):
'''Make dictionary of student responses (aka "answers")
- get is POST dictionary.
+ get is POST dictionary (Djano QueryDict).
The *get* dict has keys of the form 'x_y', which are mapped
to key 'y' in the returned dict. For example,
@@ -606,6 +606,7 @@ class CapaModule(XModule):
to 'input_1' in the returned dict)
'''
answers = dict()
+
for key in get:
# e.g. input_resistor_1 ==> resistor_1
_, _, name = key.partition('_')
@@ -613,7 +614,7 @@ class CapaModule(XModule):
# If key has no underscores, then partition
# will return (key, '', '')
# We detect this and raise an error
- if name is '':
+ if not name:
raise ValueError("%s must contain at least one underscore" % str(key))
else:
@@ -625,10 +626,7 @@ class CapaModule(XModule):
name = name[:-2] if is_list_key else name
if is_list_key:
- if type(get[key]) is list:
- val = get[key]
- else:
- val = [get[key]]
+ val = get.getlist(key)
else:
val = get[key]
diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py
index 6330511fc5..cb77921957 100644
--- a/common/lib/xmodule/xmodule/tests/test_capa_module.py
+++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py
@@ -11,6 +11,8 @@ from xmodule.capa_module import CapaModule
from xmodule.modulestore import Location
from lxml import etree
+from django.http import QueryDict
+
from . import test_system
@@ -326,14 +328,18 @@ class CapaModuleTest(unittest.TestCase):
def test_parse_get_params(self):
+ # We have to set up Django settings in order to use QueryDict
+ from django.conf import settings
+ settings.configure()
+
# Valid GET param dict
- valid_get_dict = {'input_1': 'test',
- 'input_1_2': 'test',
- 'input_1_2_3': 'test',
- 'input_[]_3': 'test',
- 'input_4': None,
- 'input_5': [],
- 'input_6': 5}
+ valid_get_dict = self._querydict_from_dict({'input_1': 'test',
+ 'input_1_2': 'test',
+ 'input_1_2_3': 'test',
+ 'input_[]_3': 'test',
+ 'input_4': None,
+ 'input_5': [],
+ 'input_6': 5})
result = CapaModule.make_dict_of_responses(valid_get_dict)
@@ -347,20 +353,19 @@ class CapaModuleTest(unittest.TestCase):
# Valid GET param dict with list keys
- valid_get_dict = {'input_2[]': ['test1', 'test2']}
+ valid_get_dict = self._querydict_from_dict({'input_2[]': ['test1', 'test2']})
result = CapaModule.make_dict_of_responses(valid_get_dict)
self.assertTrue('2' in result)
- self.assertEqual(valid_get_dict['input_2[]'], result['2'])
+ self.assertEqual(['test1','test2'], result['2'])
# If we use [] at the end of a key name, we should always
# get a list, even if there's just one value
- valid_get_dict = {'input_1[]': 'test'}
+ valid_get_dict = self._querydict_from_dict({'input_1[]': 'test'})
result = CapaModule.make_dict_of_responses(valid_get_dict)
self.assertEqual(result['1'], ['test'])
-
# If we have no underscores in the name, then the key is invalid
- invalid_get_dict = {'input': 'test'}
+ invalid_get_dict = self._querydict_from_dict({'input': 'test'})
with self.assertRaises(ValueError):
result = CapaModule.make_dict_of_responses(invalid_get_dict)
@@ -368,11 +373,32 @@ class CapaModuleTest(unittest.TestCase):
# Two equivalent names (one list, one non-list)
# One of the values would overwrite the other, so detect this
# and raise an exception
- invalid_get_dict = {'input_1[]': 'test 1',
- 'input_1': 'test 2' }
+ invalid_get_dict = self._querydict_from_dict({'input_1[]': 'test 1',
+ 'input_1': 'test 2' })
with self.assertRaises(ValueError):
result = CapaModule.make_dict_of_responses(invalid_get_dict)
+ def _querydict_from_dict(self, param_dict):
+ """ Create a Django QueryDict from a Python dictionary """
+
+ # QueryDict objects are immutable by default, so we make
+ # a copy that we can update.
+ querydict = QueryDict('')
+ copyDict = querydict.copy()
+
+ for (key, val) in param_dict.items():
+
+ # QueryDicts handle lists differently from ordinary values,
+ # so we have to specifically tell the QueryDict that
+ # this is a list
+ if type(val) is list:
+ copyDict.setlist(key, val)
+ else:
+ copyDict[key] = val
+
+ return copyDict
+
+
def test_check_problem_correct(self):
module = CapaFactory.create(attempts=1)
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index 23d27c72ac..ec2178f642 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -500,7 +500,7 @@ def modx_dispatch(request, dispatch, location, course_id):
if instance is None:
# Either permissions just changed, or someone is trying to be clever
# and load something they shouldn't have access to.
- log.debug("No module {0} for user {1}--access denied?".format(location, user))
+ log.debug("No module {0} for user {1}--access denied?".format(location, request.user))
raise Http404
instance_module = get_instance_module(course_id, request.user, instance, student_module_cache)