Merge branch 'master' into fix/btalbot/studio-sasscleanup
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -28,4 +28,5 @@ nosetests.xml
|
||||
cover_html/
|
||||
.idea/
|
||||
.redcar/
|
||||
chromedriver.log
|
||||
chromedriver.log
|
||||
ghostdriver.log
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
Then the section does not exist
|
||||
|
||||
@@ -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)
|
||||
assert_css_with_text(section_css, name)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 <startouttext /> and <endouttext />
|
||||
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 <script> tag
|
||||
xml_str = textwrap.dedent("""
|
||||
<problem>
|
||||
<script type="text/javascript">function(){}</script>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
# 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("<script type=\"text/javascript\">function(){}</script>" in etree.tostring(rendered_html))
|
||||
|
||||
|
||||
def test_render_response_xml(self):
|
||||
# Generate some XML for a string response
|
||||
kwargs = {'question_text': "Test question",
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user