merge out from master

This commit is contained in:
Brian Wilson
2013-03-27 18:04:58 -04:00
30 changed files with 842 additions and 213 deletions

View File

@@ -8,6 +8,7 @@ from functools import partial
from django.conf import settings
from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from django.http import Http404
from django.http import HttpResponse
@@ -208,9 +209,6 @@ def get_module_for_descriptor(user, request, descriptor, model_data_cache, cours
'waittime': settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS
}
def get_or_default(key, default):
getattr(settings, key, default)
#This is a hacky way to pass settings to the combined open ended xmodule
#It needs an S3 interface to upload images to S3
#It needs the open ended grading interface in order to get peer grading to be done
@@ -226,12 +224,11 @@ def get_module_for_descriptor(user, request, descriptor, model_data_cache, cours
open_ended_grading_interface['mock_staff_grading'] = settings.MOCK_STAFF_GRADING
if is_descriptor_combined_open_ended:
s3_interface = {
'access_key' : get_or_default('AWS_ACCESS_KEY_ID',''),
'secret_access_key' : get_or_default('AWS_SECRET_ACCESS_KEY',''),
'storage_bucket_name' : get_or_default('AWS_STORAGE_BUCKET_NAME','')
'access_key' : getattr(settings,'AWS_ACCESS_KEY_ID',''),
'secret_access_key' : getattr(settings,'AWS_SECRET_ACCESS_KEY',''),
'storage_bucket_name' : getattr(settings,'AWS_STORAGE_BUCKET_NAME','openended')
}
def inner_get_module(descriptor):
"""
Delegate to get_module. It does an access check, so may return None
@@ -412,6 +409,9 @@ def modx_dispatch(request, dispatch, location, course_id):
if not Location.is_valid(location):
raise Http404("Invalid location")
if not request.user.is_authenticated():
raise PermissionDenied
# Check for submitted files and basic file size checks
p = request.POST.copy()
if request.FILES:

View File

@@ -0,0 +1,107 @@
from django.test import TestCase
from django.test.client import Client
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from student.models import Registration, UserProfile
import json
class LoginTest(TestCase):
'''
Test student.views.login_user() view
'''
def setUp(self):
# Create one user and save it to the database
self.user = User.objects.create_user('test', 'test@edx.org', 'test_password')
self.user.is_active = True
self.user.save()
# Create a registration for the user
Registration().register(self.user)
# Create a profile for the user
UserProfile(user=self.user).save()
# Create the test client
self.client = Client()
# Store the login url
self.url = reverse('login')
def test_login_success(self):
response = self._login_response('test@edx.org', 'test_password')
self._assert_response(response, success=True)
def test_login_success_unicode_email(self):
unicode_email = u'test@edx.org' + unichr(40960)
self.user.email = unicode_email
self.user.save()
response = self._login_response(unicode_email, 'test_password')
self._assert_response(response, success=True)
def test_login_fail_no_user_exists(self):
response = self._login_response('not_a_user@edx.org', 'test_password')
self._assert_response(response, success=False,
value='Email or password is incorrect')
def test_login_fail_wrong_password(self):
response = self._login_response('test@edx.org', 'wrong_password')
self._assert_response(response, success=False,
value='Email or password is incorrect')
def test_login_not_activated(self):
# De-activate the user
self.user.is_active = False
self.user.save()
# Should now be unable to login
response = self._login_response('test@edx.org', 'test_password')
self._assert_response(response, success=False,
value="This account has not been activated")
def test_login_unicode_email(self):
unicode_email = u'test@edx.org' + unichr(40960)
response = self._login_response(unicode_email, 'test_password')
self._assert_response(response, success=False)
def test_login_unicode_password(self):
unicode_password = u'test_password' + unichr(1972)
response = self._login_response('test@edx.org', unicode_password)
self._assert_response(response, success=False)
def _login_response(self, email, password):
post_params = {'email': email, 'password': password}
return self.client.post(self.url, post_params)
def _assert_response(self, response, success=None, value=None):
'''
Assert that the response had status 200 and returned a valid
JSON-parseable dict.
If success is provided, assert that the response had that
value for 'success' in the JSON dict.
If value is provided, assert that the response contained that
value for 'value' in the JSON dict.
'''
self.assertEqual(response.status_code, 200)
try:
response_dict = json.loads(response.content)
except ValueError:
self.fail("Could not parse response content as JSON: %s"
% str(response.content))
if success is not None:
self.assertEqual(response_dict['success'], success)
if value is not None:
msg = ("'%s' did not contain '%s'" %
(str(response_dict['value']), str(value)))
self.assertTrue(value in response_dict['value'], msg)

View File

@@ -8,8 +8,8 @@ from django.test.client import RequestFactory
from django.test.utils import override_settings
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore.django import modulestore
import courseware.module_render as render
from xmodule.modulestore.django import modulestore, _MODULESTORES
from courseware.tests.tests import LoginEnrollmentTestCase
from courseware.model_data import ModelDataCache
@@ -40,7 +40,6 @@ TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR)
class ModuleRenderTestCase(LoginEnrollmentTestCase):
def setUp(self):
self.location = ['i4x', 'edX', 'toy', 'chapter', 'Overview']
self._MODULESTORES = {}
self.course_id = 'edX/toy/2012_Fall'
self.toy_course = modulestore().get_course(self.course_id)
@@ -91,12 +90,23 @@ class ModuleRenderTestCase(LoginEnrollmentTestCase):
self.assertEquals(render.get_score_bucket(11, 10), 'incorrect')
self.assertEquals(render.get_score_bucket(-1, 10), 'incorrect')
def test_anonymous_modx_dispatch(self):
dispatch_url = reverse(
'modx_dispatch',
args=[
'edX/toy/2012_Fall',
'i4x://edX/toy/videosequence/Toy_Videos',
'goto_position'
]
)
response = self.client.post(dispatch_url, {'position': 2})
self.assertEquals(403, response.status_code)
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
class TestTOC(TestCase):
"""Check the Table of Contents for a course"""
def setUp(self):
self._MODULESTORES = {}
# Toy courses should be loaded
self.course_name = 'edX/toy/2012_Fall'

View File

@@ -1,10 +1,24 @@
from comment_client import CommentClientError
from django_comment_client.utils import JsonError
import json
import logging
log = logging.getLogger(__name__)
class AjaxExceptionMiddleware(object):
"""
Middleware that captures CommentClientErrors during ajax requests
and tranforms them into json responses
"""
def process_exception(self, request, exception):
"""
Processes CommentClientErrors in ajax requests. If the request is an ajax request,
returns a http response that encodes the error as json
"""
if isinstance(exception, CommentClientError) and request.is_ajax():
return JsonError(json.loads(exception.message))
try:
return JsonError(json.loads(exception.message))
except ValueError:
return JsonError(exception.message)
return None

View File

@@ -1,7 +1,3 @@
import string
import random
import collections
from django.test import TestCase
import comment_client
@@ -13,17 +9,19 @@ class AjaxExceptionTestCase(TestCase):
# TODO: check whether the correct error message is produced.
# The error message should be the same as the argument to CommentClientError
def setUp(self):
self.a = middleware.AjaxExceptionMiddleware()
self.request1 = django.http.HttpRequest()
self.request0 = django.http.HttpRequest()
self.exception1 = comment_client.CommentClientError('{}')
self.exception0 = ValueError()
self.request1.META['HTTP_X_REQUESTED_WITH'] = "XMLHttpRequest"
self.request0.META['HTTP_X_REQUESTED_WITH'] = "SHADOWFAX"
def setUp(self):
self.a = middleware.AjaxExceptionMiddleware()
self.request1 = django.http.HttpRequest()
self.request0 = django.http.HttpRequest()
self.exception1 = comment_client.CommentClientError('{}')
self.exception2 = comment_client.CommentClientError('Foo!')
self.exception0 = ValueError()
self.request1.META['HTTP_X_REQUESTED_WITH'] = "XMLHttpRequest"
self.request0.META['HTTP_X_REQUESTED_WITH'] = "SHADOWFAX"
def test_process_exception(self):
self.assertIsInstance(self.a.process_exception(self.request1, self.exception1), middleware.JsonError)
self.assertIsNone(self.a.process_exception(self.request1, self.exception0))
self.assertIsNone(self.a.process_exception(self.request0, self.exception1))
self.assertIsNone(self.a.process_exception(self.request0, self.exception0))
def test_process_exception(self):
self.assertIsInstance(self.a.process_exception(self.request1, self.exception1), middleware.JsonError)
self.assertIsInstance(self.a.process_exception(self.request1, self.exception2), middleware.JsonError)
self.assertIsNone(self.a.process_exception(self.request1, self.exception0))
self.assertIsNone(self.a.process_exception(self.request0, self.exception1))
self.assertIsNone(self.a.process_exception(self.request0, self.exception0))

View File

@@ -111,7 +111,7 @@ def peer_grading(request, course_id):
#Get the peer grading modules currently in the course
items = modulestore().get_items(['i4x', None, course_id_parts[1], 'peergrading', None])
#See if any of the modules are centralized modules (ie display info from multiple problems)
items = [i for i in items if i.metadata.get("use_for_single_location", True) in false_dict]
items = [i for i in items if getattr(i,"use_for_single_location", True) in false_dict]
#Get the first one
item_location = items[0].location
#Generate a url for the first module and redirect the user to it

View File

@@ -74,6 +74,15 @@ def to_latex(x):
# LatexPrinter._print_dot = _print_dot
xs = latex(x)
xs = xs.replace(r'\XI', 'XI') # workaround for strange greek
# substitute back into latex form for scripts
# literally something of the form
# 'scriptN' becomes '\\mathcal{N}'
# note: can't use something akin to the _print_hat method above because we sometimes get 'script(N)__B' or more complicated terms
xs = re.sub(r'script([a-zA-Z0-9]+)',
'\\mathcal{\\1}',
xs)
#return '<math>%s{}{}</math>' % (xs[1:-1])
if xs[0] == '$':
return '[mathjax]%s[/mathjax]<br>' % (xs[1:-1]) # for sympy v6
@@ -106,6 +115,7 @@ def my_sympify(expr, normphase=False, matrix=False, abcsym=False, do_qubit=False
'i': sympy.I, # lowercase i is also sqrt(-1)
'Q': sympy.Symbol('Q'), # otherwise it is a sympy "ask key"
'I': sympy.Symbol('I'), # otherwise it is sqrt(-1)
'N': sympy.Symbol('N'), # or it is some kind of sympy function
#'X':sympy.sympify('Matrix([[0,1],[1,0]])'),
#'Y':sympy.sympify('Matrix([[0,-I],[I,0]])'),
#'Z':sympy.sympify('Matrix([[1,0],[0,-1]])'),
@@ -247,6 +257,127 @@ class formula(object):
fix_hat(k)
fix_hat(xml)
def flatten_pmathml(xml):
''' Give the text version of certain PMathML elements
Sometimes MathML will be given with each letter separated (it
doesn't know if its implicit multiplication or what). From an xml
node, find the (text only) variable name it represents. So it takes
<mrow>
<mi>m</mi>
<mi>a</mi>
<mi>x</mi>
</mrow>
and returns 'max', for easier use later on.
'''
tag = gettag(xml)
if tag == 'mn': return xml.text
elif tag == 'mi': return xml.text
elif tag == 'mrow': return ''.join([flatten_pmathml(y) for y in xml])
raise Exception, '[flatten_pmathml] unknown tag %s' % tag
def fix_mathvariant(parent):
'''Fix certain kinds of math variants
Literally replace <mstyle mathvariant="script"><mi>N</mi></mstyle>
with 'scriptN'. There have been problems using script_N or script(N)
'''
for child in parent:
if (gettag(child) == 'mstyle' and child.get('mathvariant') == 'script'):
newchild = etree.Element('mi')
newchild.text = 'script%s' % flatten_pmathml(child[0])
parent.replace(child, newchild)
fix_mathvariant(child)
fix_mathvariant(xml)
# find "tagged" superscripts
# they have the character \u200b in the superscript
# replace them with a__b so snuggle doesn't get confused
def fix_superscripts(xml):
''' Look for and replace sup elements with 'X__Y' or 'X_Y__Z'
In the javascript, variables with '__X' in them had an invisible
character inserted into the sup (to distinguish from powers)
E.g. normal:
<msubsup>
<mi>a</mi>
<mi>b</mi>
<mi>c</mi>
</msubsup>
to be interpreted '(a_b)^c' (nothing done by this method)
And modified:
<msubsup>
<mi>b</mi>
<mi>x</mi>
<mrow>
<mo>&#x200B;</mo>
<mi>d</mi>
</mrow>
</msubsup>
to be interpreted 'a_b__c'
also:
<msup>
<mi>x</mi>
<mrow>
<mo>&#x200B;</mo>
<mi>B</mi>
</mrow>
</msup>
to be 'x__B'
'''
for k in xml:
tag = gettag(k)
# match things like the last example--
# the second item in msub is an mrow with the first
# character equal to \u200b
if (tag == 'msup' and
len(k) == 2 and gettag(k[1]) == 'mrow' and
gettag(k[1][0]) == 'mo' and k[1][0].text == u'\u200b'): # whew
# replace the msup with 'X__Y'
k[1].remove(k[1][0])
newk = etree.Element('mi')
newk.text = '%s__%s' % (flatten_pmathml(k[0]), flatten_pmathml(k[1]))
xml.replace(k, newk)
# match things like the middle example-
# the third item in msubsup is an mrow with the first
# character equal to \u200b
if (tag == 'msubsup' and
len(k) == 3 and gettag(k[2]) == 'mrow' and
gettag(k[2][0]) == 'mo' and k[2][0].text == u'\u200b'): # whew
# replace the msubsup with 'X_Y__Z'
k[2].remove(k[2][0])
newk = etree.Element('mi')
newk.text = '%s_%s__%s' % (flatten_pmathml(k[0]), flatten_pmathml(k[1]), flatten_pmathml(k[2]))
xml.replace(k, newk)
fix_superscripts(k)
fix_superscripts(xml)
# Snuggle returns an error when it sees an <msubsup>
# replace such elements with an <msup>, except the first element is of
# the form a_b. I.e. map a_b^c => (a_b)^c
def fix_msubsup(parent):
for child in parent:
# fix msubsup
if (gettag(child) == 'msubsup' and len(child) == 3):
newchild = etree.Element('msup')
newbase = etree.Element('mi')
newbase.text = '%s_%s' % (flatten_pmathml(child[0]), flatten_pmathml(child[1]))
newexp = child[2]
newchild.append(newbase)
newchild.append(newexp)
parent.replace(child, newchild)
fix_msubsup(child)
fix_msubsup(xml)
self.xml = xml
return self.xml
@@ -257,6 +388,7 @@ class formula(object):
try:
xml = self.preprocess_pmathml(self.expr)
except Exception, err:
log.warning('Err %s while preprocessing; expr=%s' % (err, self.expr))
return "<html>Error! Cannot process pmathml</html>"
pmathml = etree.tostring(xml, pretty_print=True)
self.the_pmathml = pmathml

View File

@@ -0,0 +1,115 @@
"""
Tests of symbolic math
"""
import unittest
import formula
import re
from lxml import etree
def stripXML(xml):
xml = xml.replace('\n', '')
xml = re.sub(r'\> +\<', '><', xml)
return xml
class FormulaTest(unittest.TestCase):
# for readability later
mathml_start = '<math xmlns="http://www.w3.org/1998/Math/MathML"><mstyle displaystyle="true">'
mathml_end = '</mstyle></math>'
def setUp(self):
self.formulaInstance = formula.formula('')
def test_replace_mathvariants(self):
expr = '''
<mstyle mathvariant="script">
<mi>N</mi>
</mstyle>'''
expected = '<mi>scriptN</mi>'
# wrap
expr = stripXML(self.mathml_start + expr + self.mathml_end)
expected = stripXML(self.mathml_start + expected + self.mathml_end)
# process the expression
xml = etree.fromstring(expr)
xml = self.formulaInstance.preprocess_pmathml(xml)
test = etree.tostring(xml)
# success?
self.assertEqual(test, expected)
def test_fix_simple_superscripts(self):
expr = '''
<msup>
<mi>a</mi>
<mrow>
<mo>&#x200B;</mo>
<mi>b</mi>
</mrow>
</msup>'''
expected = '<mi>a__b</mi>'
# wrap
expr = stripXML(self.mathml_start + expr + self.mathml_end)
expected = stripXML(self.mathml_start + expected + self.mathml_end)
# process the expression
xml = etree.fromstring(expr)
xml = self.formulaInstance.preprocess_pmathml(xml)
test = etree.tostring(xml)
# success?
self.assertEqual(test, expected)
def test_fix_complex_superscripts(self):
expr = '''
<msubsup>
<mi>a</mi>
<mi>b</mi>
<mrow>
<mo>&#x200B;</mo>
<mi>c</mi>
</mrow>
</msubsup>'''
expected = '<mi>a_b__c</mi>'
# wrap
expr = stripXML(self.mathml_start + expr + self.mathml_end)
expected = stripXML(self.mathml_start + expected + self.mathml_end)
# process the expression
xml = etree.fromstring(expr)
xml = self.formulaInstance.preprocess_pmathml(xml)
test = etree.tostring(xml)
# success?
self.assertEqual(test, expected)
def test_fix_msubsup(self):
expr = '''
<msubsup>
<mi>a</mi>
<mi>b</mi>
<mi>c</mi>
</msubsup>'''
expected = '<msup><mi>a_b</mi><mi>c</mi></msup>' # which is (a_b)^c
# wrap
expr = stripXML(self.mathml_start + expr + self.mathml_end)
expected = stripXML(self.mathml_start + expected + self.mathml_end)
# process the expression
xml = etree.fromstring(expr)
xml = self.formulaInstance.preprocess_pmathml(xml)
test = etree.tostring(xml)
# success?
self.assertEqual(test, expected)