From e41172d55df9f1a0cb142b6a59625eef59dfa519 Mon Sep 17 00:00:00 2001
From: Victor Shnayder
Date: Sun, 20 Jan 2013 11:50:51 -0500
Subject: [PATCH 026/132] Add start of test framework for capa
---
.../xmodule/xmodule/tests/test_capa_module.py | 60 +++++++++++++++++++
1 file changed, 60 insertions(+)
create mode 100644 common/lib/xmodule/xmodule/tests/test_capa_module.py
diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py
new file mode 100644
index 0000000000..148fd893ff
--- /dev/null
+++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py
@@ -0,0 +1,60 @@
+import json
+from mock import Mock
+import unittest
+
+from xmodule.capa_module import CapaModule
+from xmodule.modulestore import Location
+from lxml import etree
+
+from . import test_system
+
+class CapaFactory(object):
+ """
+ A helper class to create problem modules with various parameters for testing.
+ """
+
+ sample_problem_xml = """
+
+
+
What is pi, to two decimal placs?
+
+
+
+
+
+"""
+
+ num = 0
+ @staticmethod
+ def next_num():
+ CapaFactory.num += 1
+ return CapaFactory.num
+
+ @staticmethod
+ def create():
+ definition = {'data': CapaFactory.sample_problem_xml,}
+ location = Location(["i4x", "edX", "capa_test", "problem",
+ "SampleProblem{0}".format(CapaFactory.next_num())])
+ metadata = {}
+ descriptor = Mock(weight="1")
+ instance_state = None
+
+ module = CapaModule(test_system, location,
+ definition, descriptor,
+ instance_state, None, metadata=metadata)
+
+ return module
+
+
+
+class CapaModuleTest(unittest.TestCase):
+
+ def test_import(self):
+ module = CapaFactory.create()
+ self.assertEqual(module.get_score()['score'], 0)
+
+ other_module = CapaFactory.create()
+ self.assertEqual(module.get_score()['score'], 0)
+ self.assertNotEqual(module.url_name, other_module.url_name,
+ "Factory should be creating unique names for each problem")
+
From 025b074b87b5fc60c712292d541449d0d470152b Mon Sep 17 00:00:00 2001
From: Victor Shnayder
Date: Sun, 20 Jan 2013 12:17:22 -0500
Subject: [PATCH 027/132] Add simple test for showanswer, fix test_system
---
common/lib/xmodule/xmodule/tests/__init__.py | 2 +-
.../xmodule/xmodule/tests/test_capa_module.py | 60 ++++++++++++++++++-
2 files changed, 59 insertions(+), 3 deletions(-)
diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py
index a07f1ddfaf..1f323834a9 100644
--- a/common/lib/xmodule/xmodule/tests/__init__.py
+++ b/common/lib/xmodule/xmodule/tests/__init__.py
@@ -26,7 +26,7 @@ test_system = ModuleSystem(
# "render" to just the context...
render_template=lambda template, context: str(context),
replace_urls=Mock(),
- user=Mock(),
+ user=Mock(is_staff=False),
filestore=Mock(),
debug=True,
xqueue={'interface':None, 'callback_url':'/', 'default_queuename': 'testqueue', 'waittime': 10},
diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py
index 148fd893ff..7537cb537c 100644
--- a/common/lib/xmodule/xmodule/tests/test_capa_module.py
+++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py
@@ -1,7 +1,9 @@
import json
from mock import Mock
+from pprint import pprint
import unittest
+
from xmodule.capa_module import CapaModule
from xmodule.modulestore import Location
from lxml import etree
@@ -31,13 +33,59 @@ class CapaFactory(object):
return CapaFactory.num
@staticmethod
- def create():
+ def create(graceperiod=None,
+ due=None,
+ max_attempts=None,
+ showanswer=None,
+ rerandomize=None,
+ force_save_button=None,
+ attempts=None,
+ problem_state=None,
+ ):
+ """
+ All parameters are optional, and are added to the created problem if specified.
+
+ Arguments:
+ graceperiod:
+ due:
+ max_attempts:
+ showanswer:
+ force_save_button:
+ rerandomize: all strings, as specified in the policy for the problem
+
+ problem_state: a dict to to be serialized into the instance_state of the
+ module.
+
+ attempts: also added to instance state. Should be a number.
+ """
definition = {'data': CapaFactory.sample_problem_xml,}
location = Location(["i4x", "edX", "capa_test", "problem",
"SampleProblem{0}".format(CapaFactory.next_num())])
metadata = {}
+ if graceperiod is not None:
+ metadata['graceperiod'] = graceperiod
+ if due is not None:
+ metadata['due'] = due
+ if max_attempts is not None:
+ metadata['attempts'] = max_attempts
+ if showanswer is not None:
+ metadata['showanswer'] = showanswer
+ if force_save_button is not None:
+ metadata['force_save_button'] = force_save_button
+ if rerandomize is not None:
+ metadata['rerandomize'] = rerandomize
+
+
descriptor = Mock(weight="1")
- instance_state = None
+ instance_state_dict = {}
+ if problem_state is not None:
+ instance_state_dict = problem_state
+ if attempts is not None:
+ instance_state_dict['attempts'] = attempts
+ if len(instance_state_dict) > 0:
+ instance_state = json.dumps(instance_state_dict)
+ else:
+ instance_state = None
module = CapaModule(test_system, location,
definition, descriptor,
@@ -58,3 +106,11 @@ class CapaModuleTest(unittest.TestCase):
self.assertNotEqual(module.url_name, other_module.url_name,
"Factory should be creating unique names for each problem")
+ def test_showanswer(self):
+ """
+ Make sure the show answer logic does the right thing.
+ """
+ # default, no due date, showanswer 'closed'
+ problem = CapaFactory.create()
+ pprint(problem.__dict__)
+ self.assertFalse(problem.answer_available())
From ea091a6eb83b09fbc5bafbe4f0f5011b69c8db7b Mon Sep 17 00:00:00 2001
From: Victor Shnayder
Date: Sun, 20 Jan 2013 12:49:05 -0500
Subject: [PATCH 028/132] Add tests for showanswer
---
.../xmodule/xmodule/tests/test_capa_module.py | 68 +++++++++++++++++--
1 file changed, 62 insertions(+), 6 deletions(-)
diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py
index 7537cb537c..506c7faf9f 100644
--- a/common/lib/xmodule/xmodule/tests/test_capa_module.py
+++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py
@@ -1,9 +1,9 @@
+import datetime
import json
from mock import Mock
from pprint import pprint
import unittest
-
from xmodule.capa_module import CapaModule
from xmodule.modulestore import Location
from lxml import etree
@@ -56,7 +56,7 @@ class CapaFactory(object):
problem_state: a dict to to be serialized into the instance_state of the
module.
- attempts: also added to instance state. Should be a number.
+ attempts: also added to instance state. Will be converted to an int.
"""
definition = {'data': CapaFactory.sample_problem_xml,}
location = Location(["i4x", "edX", "capa_test", "problem",
@@ -81,7 +81,9 @@ class CapaFactory(object):
if problem_state is not None:
instance_state_dict = problem_state
if attempts is not None:
- instance_state_dict['attempts'] = attempts
+ # converting to int here because I keep putting "0" and "1" in the tests
+ # since everything else is a string.
+ instance_state_dict['attempts'] = int(attempts)
if len(instance_state_dict) > 0:
instance_state = json.dumps(instance_state_dict)
else:
@@ -97,6 +99,17 @@ class CapaFactory(object):
class CapaModuleTest(unittest.TestCase):
+
+ def setUp(self):
+ now = datetime.datetime.now()
+ day_delta = datetime.timedelta(days=1)
+ self.yesterday_str = str(now - day_delta)
+ self.today_str = str(now)
+ self.tomorrow_str = str(now + day_delta)
+
+ # in the capa grace period format, not in time delta format
+ self.two_day_delta_str = "2 days"
+
def test_import(self):
module = CapaFactory.create()
self.assertEqual(module.get_score()['score'], 0)
@@ -106,11 +119,54 @@ class CapaModuleTest(unittest.TestCase):
self.assertNotEqual(module.url_name, other_module.url_name,
"Factory should be creating unique names for each problem")
- def test_showanswer(self):
+ def test_showanswer_default(self):
"""
Make sure the show answer logic does the right thing.
"""
- # default, no due date, showanswer 'closed'
+ # default, no due date, showanswer 'closed', so problem is open, and show_answer
+ # not visible.
problem = CapaFactory.create()
- pprint(problem.__dict__)
self.assertFalse(problem.answer_available())
+
+
+ def test_showanswer_attempted(self):
+ problem = CapaFactory.create(showanswer='attempted')
+ self.assertFalse(problem.answer_available())
+ problem.attempts = 1
+ self.assertTrue(problem.answer_available())
+
+
+ def test_showanswer_closed(self):
+
+ # can see after attempts used up
+ used_all_attempts = CapaFactory.create(showanswer='closed',
+ max_attempts="1",
+ attempts="1")
+ self.assertTrue(used_all_attempts.answer_available())
+
+
+ # can see after due date
+ after_due_date = CapaFactory.create(showanswer='closed',
+ max_attempts="1",
+ attempts="0",
+ due=self.yesterday_str)
+ self.assertTrue(after_due_date.answer_available())
+
+ # can't see because attempts left
+ attempts_left_open = CapaFactory.create(showanswer='closed',
+ max_attempts="1",
+ attempts="0",
+ due=self.tomorrow_str)
+ self.assertFalse(attempts_left_open.answer_available())
+
+ # Can't see because grace period hasn't expired
+ still_in_grace = CapaFactory.create(showanswer='closed',
+ max_attempts="1",
+ attempts="0",
+ due=self.yesterday_str,
+ graceperiod=self.two_day_delta_str)
+ self.assertFalse(still_in_grace.answer_available())
+
+
+
+
From 6088a926cc0697094c1bd6ae095581895fcc4563 Mon Sep 17 00:00:00 2001
From: Victor Shnayder
Date: Sun, 20 Jan 2013 17:35:03 -0500
Subject: [PATCH 029/132] Add showanswer="past_due" and tests
---
common/lib/xmodule/xmodule/capa_module.py | 35 ++++++++------
.../xmodule/xmodule/tests/test_capa_module.py | 47 ++++++++++++++++++-
2 files changed, 65 insertions(+), 17 deletions(-)
diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py
index f33da6e3a4..6d258e61ed 100644
--- a/common/lib/xmodule/xmodule/capa_module.py
+++ b/common/lib/xmodule/xmodule/capa_module.py
@@ -389,38 +389,43 @@ class CapaModule(XModule):
})
return json.dumps(d, cls=ComplexEncoder)
+ def is_past_due(self):
+ """
+ Is it now past this problem's due date, including grace period?
+ """
+ return (self.close_date is not None and
+ datetime.datetime.utcnow() > self.close_date)
+
def closed(self):
''' Is the student still allowed to submit answers? '''
if self.attempts == self.max_attempts:
return True
- if self.close_date is not None and datetime.datetime.utcnow() > self.close_date:
+ if self.is_past_due():
return True
return False
def answer_available(self):
- ''' Is the user allowed to see an answer?
+ '''
+ Is the user allowed to see an answer?
'''
if self.show_answer == '':
return False
-
- if self.show_answer == "never":
+ elif self.show_answer == "never":
return False
-
- # Admins can see the answer, unless the problem explicitly prevents it
- if self.system.user_is_staff:
+ elif self.system.user_is_staff:
+ # This i after the 'never' check because admins can see the answer
+ # unless the problem explicitly prevents it
return True
-
- if self.show_answer == 'attempted':
+ elif self.show_answer == 'attempted':
return self.attempts > 0
-
- if self.show_answer == 'answered':
+ elif self.show_answer == 'answered':
return self.lcp.done
-
- if self.show_answer == 'closed':
+ elif self.show_answer == 'closed':
return self.closed()
-
- if self.show_answer == 'always':
+ elif self.show_answer == 'past_due':
+ return self.is_past_due()
+ elif self.show_answer == 'always':
return True
return False
diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py
index 506c7faf9f..e8f639e3c9 100644
--- a/common/lib/xmodule/xmodule/tests/test_capa_module.py
+++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py
@@ -138,10 +138,11 @@ class CapaModuleTest(unittest.TestCase):
def test_showanswer_closed(self):
- # can see after attempts used up
+ # can see after attempts used up, even with due date in the future
used_all_attempts = CapaFactory.create(showanswer='closed',
max_attempts="1",
- attempts="1")
+ attempts="1",
+ due=self.tomorrow_str)
self.assertTrue(used_all_attempts.answer_available())
@@ -152,6 +153,7 @@ class CapaModuleTest(unittest.TestCase):
due=self.yesterday_str)
self.assertTrue(after_due_date.answer_available())
+
# can't see because attempts left
attempts_left_open = CapaFactory.create(showanswer='closed',
max_attempts="1",
@@ -169,4 +171,45 @@ class CapaModuleTest(unittest.TestCase):
+ def test_showanswer_past_due(self):
+ """
+ With showanswer="past_due" should only show answer after the problem is closed
+ for everyone--e.g. after due date + grace period.
+ """
+
+ # can see after attempts used up, even with due date in the future
+ used_all_attempts = CapaFactory.create(showanswer='past_due',
+ max_attempts="1",
+ attempts="1",
+ due=self.tomorrow_str)
+ self.assertFalse(used_all_attempts.answer_available())
+
+
+ # can see after due date
+ past_due_date = CapaFactory.create(showanswer='past_due',
+ max_attempts="1",
+ attempts="0",
+ due=self.yesterday_str)
+ self.assertTrue(past_due_date.answer_available())
+
+
+ # can't see because attempts left
+ attempts_left_open = CapaFactory.create(showanswer='past_due',
+ max_attempts="1",
+ attempts="0",
+ due=self.tomorrow_str)
+ self.assertFalse(attempts_left_open.answer_available())
+
+ # Can't see because grace period hasn't expired, even though have no more
+ # attempts.
+ still_in_grace = CapaFactory.create(showanswer='past_due',
+ max_attempts="1",
+ attempts="1",
+ due=self.yesterday_str,
+ graceperiod=self.two_day_delta_str)
+ self.assertFalse(still_in_grace.answer_available())
+
+
+
+
From f3f509da3b7a63b9d5a14939c02f9a9780104337 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Thu, 31 Jan 2013 12:45:48 -0500
Subject: [PATCH 030/132] Fix input area styling
---
.../xmodule/xmodule/css/combinedopenended/display.scss | 5 +++--
.../xmodule/js/src/combinedopenended/display.coffee | 2 +-
lms/static/coffee/src/open_ended/open_ended.coffee | 9 +++++----
3 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss
index 41896e6173..38fd6ba01c 100644
--- a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss
+++ b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss
@@ -442,12 +442,13 @@ section.open-ended-child {
margin: 10px;
}
- span.short-form-response {
- padding: 9px;
+ div.short-form-response {
background: #F6F6F6;
border: 1px solid #ddd;
border-top: 0;
margin-bottom: 20px;
+ overflow-y: auto;
+ height: 200px;
@include clearfix;
}
diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
index 2aabd35771..89954deb23 100644
--- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
@@ -351,5 +351,5 @@ class @CombinedOpenEnded
answer_id = @answer_area.attr('id')
answer_val = @answer_area.val()
new_text = ''
- new_text = "#{answer_val}"
+ new_text = "
')
+ blah = "blah"
gentle_alert: (msg) =>
if $('.message-container').length
From 52f3e9daafa96ee5a589e79e09aaa5611d58c229 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Thu, 31 Jan 2013 18:05:57 -0500
Subject: [PATCH 031/132] Start moving peer grading to xmodule
---
.../js/src/peergrading/peer_grading.coffee | 27 +
.../peergrading/peer_grading_problem.coffee | 478 ++++++++++++++++++
.../xmodule/xmodule/peer_grading_module.py | 439 ++++++++++++++++
.../xmodule/xmodule/peer_grading_service.py | 256 ++++++++++
4 files changed, 1200 insertions(+)
create mode 100644 common/lib/xmodule/xmodule/js/src/peergrading/peer_grading.coffee
create mode 100644 common/lib/xmodule/xmodule/js/src/peergrading/peer_grading_problem.coffee
create mode 100644 common/lib/xmodule/xmodule/peer_grading_module.py
create mode 100644 common/lib/xmodule/xmodule/peer_grading_service.py
diff --git a/common/lib/xmodule/xmodule/js/src/peergrading/peer_grading.coffee b/common/lib/xmodule/xmodule/js/src/peergrading/peer_grading.coffee
new file mode 100644
index 0000000000..ed79ba9c71
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/src/peergrading/peer_grading.coffee
@@ -0,0 +1,27 @@
+# This is a simple class that just hides the error container
+# and message container when they are empty
+# Can (and should be) expanded upon when our problem list
+# becomes more sophisticated
+class PeerGrading
+ constructor: () ->
+ @error_container = $('.error-container')
+ @error_container.toggle(not @error_container.is(':empty'))
+
+ @message_container = $('.message-container')
+ @message_container.toggle(not @message_container.is(':empty'))
+
+ @problem_list = $('.problem-list')
+ @construct_progress_bar()
+
+ construct_progress_bar: () =>
+ problems = @problem_list.find('tr').next()
+ problems.each( (index, element) =>
+ problem = $(element)
+ progress_bar = problem.find('.progress-bar')
+ bar_value = parseInt(problem.data('graded'))
+ bar_max = parseInt(problem.data('required')) + bar_value
+ progress_bar.progressbar({value: bar_value, max: bar_max})
+ )
+
+
+$(document).ready(() -> new PeerGrading())
diff --git a/common/lib/xmodule/xmodule/js/src/peergrading/peer_grading_problem.coffee b/common/lib/xmodule/xmodule/js/src/peergrading/peer_grading_problem.coffee
new file mode 100644
index 0000000000..ab16b34d12
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/src/peergrading/peer_grading_problem.coffee
@@ -0,0 +1,478 @@
+##################################
+#
+# This is the JS that renders the peer grading problem page.
+# Fetches the correct problem and/or calibration essay
+# and sends back the grades
+#
+# Should not be run when we don't have a location to send back
+# to the server
+#
+# PeerGradingProblemBackend -
+# makes all the ajax requests and provides a mock interface
+# for testing purposes
+#
+# PeerGradingProblem -
+# handles the rendering and user interactions with the interface
+#
+##################################
+class PeerGradingProblemBackend
+ constructor: (ajax_url, mock_backend) ->
+ @mock_backend = mock_backend
+ @ajax_url = ajax_url
+ @mock_cnt = 0
+
+ post: (cmd, data, callback) ->
+ if @mock_backend
+ callback(@mock(cmd, data))
+ else
+ # if this post request fails, the error callback will catch it
+ $.post(@ajax_url + cmd, data, callback)
+ .error => callback({success: false, error: "Error occured while performing this operation"})
+
+ mock: (cmd, data) ->
+ if cmd == 'is_student_calibrated'
+ # change to test each version
+ response =
+ success: true
+ calibrated: @mock_cnt >= 2
+ else if cmd == 'show_calibration_essay'
+ #response =
+ # success: false
+ # error: "There was an error"
+ @mock_cnt++
+ response =
+ success: true
+ submission_id: 1
+ submission_key: 'abcd'
+ student_response: '''
+ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.
+
+The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.
+ '''
+ prompt: '''
+
S11E3: Metal Bands
+
Shown below are schematic band diagrams for two different metals. Both diagrams appear different, yet both of the elements are undisputably metallic in nature.
+
* Why is it that both sodium and magnesium behave as metals, even though the s-band of magnesium is filled?
+
This is a self-assessed open response question. Please use as much space as you need in the box below to answer the question.
+ '''
+ rubric: '''
+
Purpose
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Organization
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ '''
+ max_score: 4
+ else if cmd == 'get_next_submission'
+ response =
+ success: true
+ submission_id: 1
+ submission_key: 'abcd'
+ student_response: '''Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nec tristique ante. Proin at mauris sapien, quis varius leo. Morbi laoreet leo nisi. Morbi aliquam lacus ante. Cras iaculis velit sed diam mattis a fermentum urna luctus. Duis consectetur nunc vitae felis facilisis eget vulputate risus viverra. Cras consectetur ullamcorper lobortis. Nam eu gravida lorem. Nulla facilisi. Nullam quis felis enim. Mauris orci lectus, dictum id cursus in, vulputate in massa.
+
+Phasellus non varius sem. Nullam commodo lacinia odio sit amet egestas. Donec ullamcorper sapien sagittis arcu volutpat placerat. Phasellus ut pretium ante. Nam dictum pulvinar nibh dapibus tristique. Sed at tellus mi, fringilla convallis justo. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus tristique rutrum nulla sed eleifend. Praesent at nunc arcu. Mauris condimentum faucibus nibh, eget commodo quam viverra sed. Morbi in tincidunt dolor. Morbi sed augue et augue interdum fermentum.
+
+Curabitur tristique purus ac arcu consequat cursus. Cras diam felis, dignissim quis placerat at, aliquet ac metus. Mauris vulputate est eu nibh imperdiet varius. Cras aliquet rhoncus elit a laoreet. Mauris consectetur erat et erat scelerisque eu faucibus dolor consequat. Nam adipiscing sagittis nisl, eu mollis massa tempor ac. Nulla scelerisque tempus blandit. Phasellus ac ipsum eros, id posuere arcu. Nullam non sapien arcu. Vivamus sit amet lorem justo, ac tempus turpis. Suspendisse pharetra gravida imperdiet. Pellentesque lacinia mi eu elit luctus pellentesque. Sed accumsan libero a magna elementum varius. Nunc eget pellentesque metus. '''
+ prompt: '''
+
S11E3: Metal Bands
+
Shown below are schematic band diagrams for two different metals. Both diagrams appear different, yet both of the elements are undisputably metallic in nature.
+
* Why is it that both sodium and magnesium behave as metals, even though the s-band of magnesium is filled?
+
This is a self-assessed open response question. Please use as much space as you need in the box below to answer the question.
+ '''
+ rubric: '''
+
Purpose
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Organization
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ '''
+ max_score: 4
+ else if cmd == 'save_calibration_essay'
+ response =
+ success: true
+ actual_score: 2
+ else if cmd == 'save_grade'
+ response =
+ success: true
+
+ return response
+
+
+class PeerGradingProblem
+ constructor: (backend) ->
+ @prompt_wrapper = $('.prompt-wrapper')
+ @backend = backend
+
+
+ # get the location of the problem
+ @location = $('.peer-grading').data('location')
+ # prevent this code from trying to run
+ # when we don't have a location
+ if(!@location)
+ return
+
+ # get the other elements we want to fill in
+ @submission_container = $('.submission-container')
+ @prompt_container = $('.prompt-container')
+ @rubric_container = $('.rubric-container')
+ @flag_student_container = $('.flag-student-container')
+ @calibration_panel = $('.calibration-panel')
+ @grading_panel = $('.grading-panel')
+ @content_panel = $('.content-panel')
+ @grading_message = $('.grading-message')
+ @grading_message.hide()
+
+ @grading_wrapper =$('.grading-wrapper')
+ @calibration_feedback_panel = $('.calibration-feedback')
+ @interstitial_page = $('.interstitial-page')
+ @interstitial_page.hide()
+
+ @error_container = $('.error-container')
+
+ @submission_key_input = $("input[name='submission-key']")
+ @essay_id_input = $("input[name='essay-id']")
+ @feedback_area = $('.feedback-area')
+
+ @score_selection_container = $('.score-selection-container')
+ @rubric_selection_container = $('.rubric-selection-container')
+ @grade = null
+ @calibration = null
+
+ @submit_button = $('.submit-button')
+ @action_button = $('.action-button')
+ @calibration_feedback_button = $('.calibration-feedback-button')
+ @interstitial_page_button = $('.interstitial-page-button')
+ @flag_student_checkbox = $('.flag-checkbox')
+
+ Collapsible.setCollapsibles(@content_panel)
+
+ # Set up the click event handlers
+ @action_button.click -> history.back()
+ @calibration_feedback_button.click =>
+ @calibration_feedback_panel.hide()
+ @grading_wrapper.show()
+ @is_calibrated_check()
+
+ @interstitial_page_button.click =>
+ @interstitial_page.hide()
+ @is_calibrated_check()
+
+ @is_calibrated_check()
+
+
+ ##########
+ #
+ # Ajax calls to the backend
+ #
+ ##########
+ is_calibrated_check: () =>
+ @backend.post('is_student_calibrated', {location: @location}, @calibration_check_callback)
+
+ fetch_calibration_essay: () =>
+ @backend.post('show_calibration_essay', {location: @location}, @render_calibration)
+
+ fetch_submission_essay: () =>
+ @backend.post('get_next_submission', {location: @location}, @render_submission)
+
+ # finds the scores for each rubric category
+ get_score_list: () =>
+ # find the number of categories:
+ num_categories = $('table.rubric tr').length
+
+ score_lst = []
+ # get the score for each one
+ for i in [0..(num_categories-1)]
+ score = $("input[name='score-selection-#{i}']:checked").val()
+ score_lst.push(score)
+
+ return score_lst
+
+ construct_data: () ->
+ data =
+ rubric_scores: @get_score_list()
+ score: @grade
+ location: @location
+ submission_id: @essay_id_input.val()
+ submission_key: @submission_key_input.val()
+ feedback: @feedback_area.val()
+ submission_flagged: @flag_student_checkbox.is(':checked')
+ return data
+
+
+ submit_calibration_essay: ()=>
+ data = @construct_data()
+ @backend.post('save_calibration_essay', data, @calibration_callback)
+
+ submit_grade: () =>
+ data = @construct_data()
+ @backend.post('save_grade', data, @submission_callback)
+
+
+ ##########
+ #
+ # Callbacks for various events
+ #
+ ##########
+
+ # called after we perform an is_student_calibrated check
+ calibration_check_callback: (response) =>
+ if response.success
+ # if we haven't been calibrating before
+ if response.calibrated and (@calibration == null or @calibration == false)
+ @calibration = false
+ @fetch_submission_essay()
+ # If we were calibrating before and no longer need to,
+ # show the interstitial page
+ else if response.calibrated and @calibration == true
+ @calibration = false
+ @render_interstitial_page()
+ else
+ @calibration = true
+ @fetch_calibration_essay()
+ else if response.error
+ @render_error(response.error)
+ else
+ @render_error("Error contacting the grading service")
+
+
+ # called after we submit a calibration score
+ calibration_callback: (response) =>
+ if response.success
+ @render_calibration_feedback(response)
+ else if response.error
+ @render_error(response.error)
+ else
+ @render_error("Error saving calibration score")
+
+ # called after we submit a submission score
+ submission_callback: (response) =>
+ if response.success
+ @is_calibrated_check()
+ @grading_message.fadeIn()
+ @grading_message.html("
Grade sent successfully.
")
+ else
+ if response.error
+ @render_error(response.error)
+ else
+ @render_error("Error occurred while submitting grade")
+
+ # called after a grade is selected on the interface
+ graded_callback: (event) =>
+ @grade = $("input[name='grade-selection']:checked").val()
+ if @grade == undefined
+ return
+ # check to see whether or not any categories have not been scored
+ num_categories = $('table.rubric tr').length
+ for i in [0..(num_categories-1)]
+ score = $("input[name='score-selection-#{i}']:checked").val()
+ if score == undefined
+ return
+ # show button if we have scores for all categories
+ @show_submit_button()
+
+
+
+ ##########
+ #
+ # Rendering methods and helpers
+ #
+ ##########
+ # renders a calibration essay
+ render_calibration: (response) =>
+ if response.success
+
+ # load in all the data
+ @submission_container.html("
Training Essay
")
+ @render_submission_data(response)
+ # TODO: indicate that we're in calibration mode
+ @calibration_panel.addClass('current-state')
+ @grading_panel.removeClass('current-state')
+
+ # Display the right text
+ # both versions of the text are written into the template itself
+ # we only need to show/hide the correct ones at the correct time
+ @calibration_panel.find('.calibration-text').show()
+ @grading_panel.find('.calibration-text').show()
+ @calibration_panel.find('.grading-text').hide()
+ @grading_panel.find('.grading-text').hide()
+ @flag_student_container.hide()
+
+ @submit_button.unbind('click')
+ @submit_button.click @submit_calibration_essay
+
+ else if response.error
+ @render_error(response.error)
+ else
+ @render_error("An error occurred while retrieving the next calibration essay")
+
+ # Renders a student submission to be graded
+ render_submission: (response) =>
+ if response.success
+ @submit_button.hide()
+ @submission_container.html("
Submitted Essay
")
+ @render_submission_data(response)
+
+ @calibration_panel.removeClass('current-state')
+ @grading_panel.addClass('current-state')
+
+ # Display the correct text
+ # both versions of the text are written into the template itself
+ # we only need to show/hide the correct ones at the correct time
+ @calibration_panel.find('.calibration-text').hide()
+ @grading_panel.find('.calibration-text').hide()
+ @calibration_panel.find('.grading-text').show()
+ @grading_panel.find('.grading-text').show()
+ @flag_student_container.show()
+
+ @submit_button.unbind('click')
+ @submit_button.click @submit_grade
+ else if response.error
+ @render_error(response.error)
+ else
+ @render_error("An error occured when retrieving the next submission.")
+
+
+ make_paragraphs: (text) ->
+ paragraph_split = text.split(/\n\s*\n/)
+ new_text = ''
+ for paragraph in paragraph_split
+ new_text += "
From c1583dbba2861434fb37635d031f7b2b7a61c50b Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Thu, 31 Jan 2013 19:57:35 -0500
Subject: [PATCH 039/132] Properly load javascript, fix templates to work with
xmodule, modify AJAX handlers
---
.../js/src/peergrading/peer_grading.coffee | 14 +-
.../peergrading/peer_grading_problem.coffee | 229 +++++++++---------
.../xmodule/xmodule/peer_grading_module.py | 73 +++---
.../xmodule/xmodule/peer_grading_service.py | 2 +
lms/templates/peer_grading/peer_grading.html | 2 +-
.../peer_grading/peer_grading_problem.html | 2 +-
6 files changed, 159 insertions(+), 163 deletions(-)
diff --git a/common/lib/xmodule/xmodule/js/src/peergrading/peer_grading.coffee b/common/lib/xmodule/xmodule/js/src/peergrading/peer_grading.coffee
index 113f5e02a6..b8196838f3 100644
--- a/common/lib/xmodule/xmodule/js/src/peergrading/peer_grading.coffee
+++ b/common/lib/xmodule/xmodule/js/src/peergrading/peer_grading.coffee
@@ -2,11 +2,11 @@
# and message container when they are empty
# Can (and should be) expanded upon when our problem list
# becomes more sophisticated
-class PeerGrading
- constructor: () ->
+class @PeerGrading
+ constructor: (element) ->
@peer_grading_container = $('.peer-grading')
@peer_grading_outer_container = $('.peer-grading-container')
- @ajax_url = peer_grading_container.data('ajax-url')
+ @ajax_url = @peer_grading_container.data('ajax-url')
@error_container = $('.error-container')
@error_container.toggle(not @error_container.is(':empty'))
@@ -14,7 +14,7 @@ class PeerGrading
@message_container.toggle(not @message_container.is(':empty'))
@problem_button = $('.problem-button')
- @problem_button.click show_results
+ @problem_button.click @show_results
@problem_list = $('.problem-list')
@construct_progress_bar()
@@ -35,7 +35,7 @@ class PeerGrading
$.postWithPrefix "#{@ajax_url}problem", data, (response) =>
if response.success
@peer_grading_outer_container.after(response.html).remove()
+ backend = new PeerGradingProblemBackend(@ajax_url, false)
+ new PeerGradingProblem(backend)
else
- @gentle_alert response.error
-
-$(document).ready(() -> new PeerGrading())
+ @gentle_alert response.error
\ No newline at end of file
diff --git a/common/lib/xmodule/xmodule/js/src/peergrading/peer_grading_problem.coffee b/common/lib/xmodule/xmodule/js/src/peergrading/peer_grading_problem.coffee
index ab16b34d12..ee98905cda 100644
--- a/common/lib/xmodule/xmodule/js/src/peergrading/peer_grading_problem.coffee
+++ b/common/lib/xmodule/xmodule/js/src/peergrading/peer_grading_problem.coffee
@@ -7,7 +7,7 @@
# Should not be run when we don't have a location to send back
# to the server
#
-# PeerGradingProblemBackend -
+# PeerGradingProblemBackend -
# makes all the ajax requests and provides a mock interface
# for testing purposes
#
@@ -15,7 +15,7 @@
# handles the rendering and user interactions with the interface
#
##################################
-class PeerGradingProblemBackend
+class @PeerGradingProblemBackend
constructor: (ajax_url, mock_backend) ->
@mock_backend = mock_backend
@ajax_url = ajax_url
@@ -32,141 +32,140 @@ class PeerGradingProblemBackend
mock: (cmd, data) ->
if cmd == 'is_student_calibrated'
# change to test each version
- response =
- success: true
+ response =
+ success: true
calibrated: @mock_cnt >= 2
else if cmd == 'show_calibration_essay'
- #response =
+ #response =
# success: false
# error: "There was an error"
@mock_cnt++
- response =
+ response =
success: true
submission_id: 1
submission_key: 'abcd'
student_response: '''
- Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.
+ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.
-The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.
- '''
+ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.
+ '''
prompt: '''
-
S11E3: Metal Bands
-
Shown below are schematic band diagrams for two different metals. Both diagrams appear different, yet both of the elements are undisputably metallic in nature.
-
* Why is it that both sodium and magnesium behave as metals, even though the s-band of magnesium is filled?
-
This is a self-assessed open response question. Please use as much space as you need in the box below to answer the question.
- '''
+
S11E3: Metal Bands
+
Shown below are schematic band diagrams for two different metals. Both diagrams appear different, yet both of the elements are undisputably metallic in nature.
+
* Why is it that both sodium and magnesium behave as metals, even though the s-band of magnesium is filled?
+
This is a self-assessed open response question. Please use as much space as you need in the box below to answer the question.
+ '''
rubric: '''
-
Purpose
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Organization
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- '''
+
Purpose
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Organization
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ '''
max_score: 4
else if cmd == 'get_next_submission'
- response =
+ response =
success: true
submission_id: 1
submission_key: 'abcd'
student_response: '''Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nec tristique ante. Proin at mauris sapien, quis varius leo. Morbi laoreet leo nisi. Morbi aliquam lacus ante. Cras iaculis velit sed diam mattis a fermentum urna luctus. Duis consectetur nunc vitae felis facilisis eget vulputate risus viverra. Cras consectetur ullamcorper lobortis. Nam eu gravida lorem. Nulla facilisi. Nullam quis felis enim. Mauris orci lectus, dictum id cursus in, vulputate in massa.
-Phasellus non varius sem. Nullam commodo lacinia odio sit amet egestas. Donec ullamcorper sapien sagittis arcu volutpat placerat. Phasellus ut pretium ante. Nam dictum pulvinar nibh dapibus tristique. Sed at tellus mi, fringilla convallis justo. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus tristique rutrum nulla sed eleifend. Praesent at nunc arcu. Mauris condimentum faucibus nibh, eget commodo quam viverra sed. Morbi in tincidunt dolor. Morbi sed augue et augue interdum fermentum.
+ Phasellus non varius sem. Nullam commodo lacinia odio sit amet egestas. Donec ullamcorper sapien sagittis arcu volutpat placerat. Phasellus ut pretium ante. Nam dictum pulvinar nibh dapibus tristique. Sed at tellus mi, fringilla convallis justo. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus tristique rutrum nulla sed eleifend. Praesent at nunc arcu. Mauris condimentum faucibus nibh, eget commodo quam viverra sed. Morbi in tincidunt dolor. Morbi sed augue et augue interdum fermentum.
-Curabitur tristique purus ac arcu consequat cursus. Cras diam felis, dignissim quis placerat at, aliquet ac metus. Mauris vulputate est eu nibh imperdiet varius. Cras aliquet rhoncus elit a laoreet. Mauris consectetur erat et erat scelerisque eu faucibus dolor consequat. Nam adipiscing sagittis nisl, eu mollis massa tempor ac. Nulla scelerisque tempus blandit. Phasellus ac ipsum eros, id posuere arcu. Nullam non sapien arcu. Vivamus sit amet lorem justo, ac tempus turpis. Suspendisse pharetra gravida imperdiet. Pellentesque lacinia mi eu elit luctus pellentesque. Sed accumsan libero a magna elementum varius. Nunc eget pellentesque metus. '''
+ Curabitur tristique purus ac arcu consequat cursus. Cras diam felis, dignissim quis placerat at, aliquet ac metus. Mauris vulputate est eu nibh imperdiet varius. Cras aliquet rhoncus elit a laoreet. Mauris consectetur erat et erat scelerisque eu faucibus dolor consequat. Nam adipiscing sagittis nisl, eu mollis massa tempor ac. Nulla scelerisque tempus blandit. Phasellus ac ipsum eros, id posuere arcu. Nullam non sapien arcu. Vivamus sit amet lorem justo, ac tempus turpis. Suspendisse pharetra gravida imperdiet. Pellentesque lacinia mi eu elit luctus pellentesque. Sed accumsan libero a magna elementum varius. Nunc eget pellentesque metus. '''
prompt: '''
-
S11E3: Metal Bands
-
Shown below are schematic band diagrams for two different metals. Both diagrams appear different, yet both of the elements are undisputably metallic in nature.
-
* Why is it that both sodium and magnesium behave as metals, even though the s-band of magnesium is filled?
-
This is a self-assessed open response question. Please use as much space as you need in the box below to answer the question.
- '''
+
S11E3: Metal Bands
+
Shown below are schematic band diagrams for two different metals. Both diagrams appear different, yet both of the elements are undisputably metallic in nature.
+
* Why is it that both sodium and magnesium behave as metals, even though the s-band of magnesium is filled?
+
This is a self-assessed open response question. Please use as much space as you need in the box below to answer the question.
+ '''
rubric: '''
-
Purpose
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Organization
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- '''
+
Purpose
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Organization
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ '''
max_score: 4
else if cmd == 'save_calibration_essay'
- response =
+ response =
success: true
actual_score: 2
else if cmd == 'save_grade'
- response =
+ response =
success: true
return response
-
-class PeerGradingProblem
+class @PeerGradingProblem
constructor: (backend) ->
@prompt_wrapper = $('.prompt-wrapper')
@backend = backend
-
+
# get the location of the problem
@location = $('.peer-grading').data('location')
- # prevent this code from trying to run
+ # prevent this code from trying to run
# when we don't have a location
if(!@location)
return
@@ -208,7 +207,7 @@ class PeerGradingProblem
# Set up the click event handlers
@action_button.click -> history.back()
- @calibration_feedback_button.click =>
+ @calibration_feedback_button.click =>
@calibration_feedback_panel.hide()
@grading_wrapper.show()
@is_calibrated_check()
@@ -266,7 +265,7 @@ class PeerGradingProblem
submit_grade: () =>
data = @construct_data()
@backend.post('save_grade', data, @submission_callback)
-
+
##########
#
@@ -301,7 +300,7 @@ class PeerGradingProblem
@render_calibration_feedback(response)
else if response.error
@render_error(response.error)
- else
+ else
@render_error("Error saving calibration score")
# called after we submit a submission score
@@ -330,8 +329,8 @@ class PeerGradingProblem
# show button if we have scores for all categories
@show_submit_button()
-
-
+
+
##########
#
# Rendering methods and helpers
@@ -344,7 +343,7 @@ class PeerGradingProblem
# load in all the data
@submission_container.html("
Training Essay
")
@render_submission_data(response)
- # TODO: indicate that we're in calibration mode
+ # TODO: indicate that we're in calibration mode
@calibration_panel.addClass('current-state')
@grading_panel.removeClass('current-state')
@@ -428,12 +427,12 @@ class PeerGradingProblem
if score == actual_score
calibration_wrapper.append("
Congratulations! Your score matches the actual score!
")
else
- calibration_wrapper.append("
Please try to understand the grading critera better to be more accurate next time.
")
+ calibration_wrapper.append("
Please try to understand the grading critera better to be more accurate next time.
")
# disable score selection and submission from the grading interface
$("input[name='score-selection']").attr('disabled', true)
@submit_button.hide()
-
+
render_interstitial_page: () =>
@content_panel.hide()
@interstitial_page.show()
@@ -449,7 +448,7 @@ class PeerGradingProblem
@submit_button.show()
setup_score_selection: (max_score) =>
-
+
# first, get rid of all the old inputs, if any.
@score_selection_container.html("""
Overall Score
@@ -460,7 +459,7 @@ class PeerGradingProblem
for score in [0..max_score]
id = 'score-' + score
label = """"""
-
+
input = """
""" # " fix broken parsing in emacs
@@ -470,9 +469,7 @@ class PeerGradingProblem
$("input[name='score-selection']").change @graded_callback
$("input[name='grade-selection']").change @graded_callback
-
-
-mock_backend = false
-ajax_url = $('.peer-grading').data('ajax_url')
-backend = new PeerGradingProblemBackend(ajax_url, mock_backend)
-$(document).ready(() -> new PeerGradingProblem(backend))
+#mock_backend = false
+#ajax_url = $('.peer-grading').data('ajax_url')
+#backend = new PeerGradingProblemBackend(ajax_url, mock_backend)
+#$(document).ready(() -> new PeerGradingProblem(backend))
diff --git a/common/lib/xmodule/xmodule/peer_grading_module.py b/common/lib/xmodule/xmodule/peer_grading_module.py
index c5a08e0812..be09751e29 100644
--- a/common/lib/xmodule/xmodule/peer_grading_module.py
+++ b/common/lib/xmodule/xmodule/peer_grading_module.py
@@ -68,7 +68,6 @@ class PeerGradingModule(XModule):
system.set('location', location)
self.system = system
self.peer_gs = peer_grading_service()
- log.debug(self.system)
self.use_for_single_location = self.metadata.get('use_for_single_location', USE_FOR_SINGLE_LOCATION)
if isinstance(self.use_for_single_location, basestring):
@@ -108,7 +107,7 @@ class PeerGradingModule(XModule):
Needs to be implemented by child modules. Handles AJAX events.
@return:
"""
-
+ log.debug(get)
handlers = {
'get_next_submission': self.get_next_submission,
'show_calibration_essay': self.show_calibration_essay,
@@ -123,6 +122,8 @@ class PeerGradingModule(XModule):
d = handlers[dispatch](get)
+ log.debug(d)
+
return json.dumps(d, cls=ComplexEncoder)
def get_progress(self):
@@ -149,14 +150,12 @@ class PeerGradingModule(XModule):
'error': if success is False, will have an error message with more info.
"""
- _check_post(request)
required = set(['location'])
- success, message = _check_required(request, required)
+ success, message = self._check_required(get, required)
if not success:
return _err_response(message)
- grader_id = unique_id_for_user(request.user)
- p = request.POST
- location = p['location']
+ grader_id = self.system.anonymous_student_id
+ location = get['location']
try:
response = self.peer_gs.get_next_submission(location, grader_id)
@@ -183,20 +182,20 @@ class PeerGradingModule(XModule):
success: bool indicating whether the save was a success
error: if there was an error in the submission, this is the error message
"""
- _check_post(request)
+
required = set(['location', 'submission_id', 'submission_key', 'score', 'feedback', 'rubric_scores[]', 'submission_flagged'])
- success, message = _check_required(request, required)
+ success, message = self._check_required(get, required)
if not success:
return _err_response(message)
- grader_id = unique_id_for_user(request.user)
- p = request.POST
- location = p['location']
- submission_id = p['submission_id']
- score = p['score']
- feedback = p['feedback']
- submission_key = p['submission_key']
- rubric_scores = p.getlist('rubric_scores[]')
- submission_flagged = p['submission_flagged']
+ grader_id = self.system.anonymous_student_id
+
+ location = get['location']
+ submission_id = get['submission_id']
+ score = get['score']
+ feedback = get['feedback']
+ submission_key = get['submission_key']
+ rubric_scores = get['rubric_scores']
+ submission_flagged = get['submission_flagged']
try:
response = self.peer_gs.save_grade(location, grader_id, submission_id,
score, feedback, submission_key, rubric_scores, submission_flagged)
@@ -227,14 +226,14 @@ class PeerGradingModule(XModule):
total_calibrated_on_so_far - the number of calibration essays for this problem
that this grader has graded
"""
- _check_post(request)
+
required = set(['location'])
- success, message = _check_required(request, required)
+ success, message = self._check_required(get, required)
if not success:
return _err_response(message)
- grader_id = unique_id_for_user(request.user)
- p = request.POST
- location = p['location']
+ grader_id = self.system.anonymous_student_id
+
+ location = get['location']
try:
response = self.peer_gs.is_student_calibrated(location, grader_id)
@@ -268,16 +267,15 @@ class PeerGradingModule(XModule):
'error': if success is False, will have an error message with more info.
"""
- _check_post(request)
required = set(['location'])
- success, message = _check_required(request, required)
+ success, message = self._check_required(get, required)
if not success:
return _err_response(message)
- grader_id = unique_id_for_user(request.user)
- p = request.POST
- location = p['location']
+ grader_id = self.system.anonymous_student_id
+
+ location = get['location']
try:
response = self.peer_gs.show_calibration_essay(location, grader_id)
return HttpResponse(response, mimetype="application/json")
@@ -311,20 +309,19 @@ class PeerGradingModule(XModule):
actual_score: the score that the instructor gave to this calibration essay
"""
- _check_post(request)
required = set(['location', 'submission_id', 'submission_key', 'score', 'feedback', 'rubric_scores[]'])
- success, message = _check_required(request, required)
+ success, message = self._check_required(get, required)
if not success:
return _err_response(message)
- grader_id = unique_id_for_user(request.user)
- p = request.POST
- location = p['location']
- calibration_essay_id = p['submission_id']
- submission_key = p['submission_key']
- score = p['score']
- feedback = p['feedback']
- rubric_scores = p.getlist('rubric_scores[]')
+ grader_id = self.system.anonymous_student_id
+
+ location = get['location']
+ calibration_essay_id = get['submission_id']
+ submission_key = get['submission_key']
+ score = get['score']
+ feedback = get['feedback']
+ rubric_scores = get['rubric_scores']
try:
response = self.peer_gs.save_calibration_essay(location, grader_id, calibration_essay_id,
diff --git a/common/lib/xmodule/xmodule/peer_grading_service.py b/common/lib/xmodule/xmodule/peer_grading_service.py
index 172a981a96..a8e74dd3cc 100644
--- a/common/lib/xmodule/xmodule/peer_grading_service.py
+++ b/common/lib/xmodule/xmodule/peer_grading_service.py
@@ -48,6 +48,7 @@ class PeerGradingService():
'rubric_scores': rubric_scores,
'rubric_scores_complete': True,
'submission_flagged' : submission_flagged}
+ log.debug(data)
return self.post(self.save_grade_url, data)
def is_student_calibrated(self, problem_location, grader_id):
@@ -69,6 +70,7 @@ class PeerGradingService():
'feedback': feedback,
'rubric_scores[]': rubric_scores,
'rubric_scores_complete': True}
+ log.debug(data)
return self.post(self.save_calibration_essay_url, data)
def get_problem_list(self, course_id, grader_id):
diff --git a/lms/templates/peer_grading/peer_grading.html b/lms/templates/peer_grading/peer_grading.html
index 99ef288e5f..1dd74d74e4 100644
--- a/lms/templates/peer_grading/peer_grading.html
+++ b/lms/templates/peer_grading/peer_grading.html
@@ -1,5 +1,5 @@
-
Consider a hypothetical magnetic field pointing out of your computer screen. Now imagine an electron traveling from right to left in the plane of your screen. A diagram of this situation is show below…
+
+
+
a. The magnitude of the force experienced by the electron is proportional the product of which of the following? (Select all that apply.)
+
+
+
+
+Magnetic field strength…
+Electric field strength…
+Electric charge of the electron…
+Radius of the electron…
+Mass of the electron…
+Velocity of the electron…
+
+
+
+
+
From e59b0b87b18b7df0244cb8462ede99166db44115 Mon Sep 17 00:00:00 2001
From: ichuang
Date: Sun, 3 Feb 2013 21:07:23 -0500
Subject: [PATCH 049/132] add docs for conditional module; clean up a bit more
---
.../lib/xmodule/xmodule/conditional_module.py | 6 ------
doc/xml-format.md | 17 +++++++++++++++++
2 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/common/lib/xmodule/xmodule/conditional_module.py b/common/lib/xmodule/xmodule/conditional_module.py
index 0a241c99de..e20681e614 100644
--- a/common/lib/xmodule/xmodule/conditional_module.py
+++ b/common/lib/xmodule/xmodule/conditional_module.py
@@ -43,7 +43,6 @@ class ConditionalModule(XModule):
"""
XModule.__init__(self, system, location, definition, descriptor, instance_state, shared_state, **kwargs)
self.contents = None
- #self.required_modules_list = [tuple(x.split('/',1)) for x in self.metadata.get('required','').split('&')]
self.condition = self.metadata.get('condition','')
#log.debug('conditional module required=%s' % self.required_modules_list)
@@ -105,9 +104,7 @@ class ConditionalModule(XModule):
if not self.is_condition_satisfied():
context = {'module': self}
html = self.system.render_template('conditional_module.html', context)
- # html = render_to_string('conditional_module.html', context)
return json.dumps({'html': html})
- #return self.system.render_template('conditional_module.html', context)
if self.contents is None:
self.contents = [child.get_html() for child in self.get_display_items()]
@@ -115,10 +112,7 @@ class ConditionalModule(XModule):
# for now, just deal with one child
html = self.contents[0]
- #log.debug('rendered conditional module %s' % str(self.location))
-
return json.dumps({'html': html})
- #return html
class ConditionalDescriptor(SequenceDescriptor):
module_class = ConditionalModule
diff --git a/doc/xml-format.md b/doc/xml-format.md
index f4fd1054cb..b93f3bbeab 100644
--- a/doc/xml-format.md
+++ b/doc/xml-format.md
@@ -141,6 +141,7 @@ That's basically all there is to the organizational structure. Read the next se
* `abtest` -- Support for A/B testing. TODO: add details..
* `chapter` -- top level organization unit of a course. The courseware display code currently expects the top level `course` element to contain only chapters, though there is no philosophical reason why this is required, so we may change it to properly display non-chapters at the top level.
+* `conditional` -- conditional element, which shows one or more modules only if certain conditions are satisfied.
* `course` -- top level tag. Contains everything else.
* `customtag` -- render an html template, filling in some parameters, and return the resulting html. See below for details.
* `discussion` -- Inline discussion forum
@@ -163,6 +164,22 @@ Container tags include `chapter`, `sequential`, `videosequence`, `vertical`, and
`course` is also a container, and is similar, with one extra wrinkle: the top level pointer tag _must_ have `org` and `course` attributes specified--the organization name, and course name. Note that `course` is referring to the platonic ideal of this course (e.g. "6.002x"), not to any particular run of this course. The `url_name` should be the particular run of this course.
+### `conditional`
+
+`conditional` is as special kind of container tag as well. Here are two examples:
+
+
+
+
+
+
+
+
+
+The condition can be either `require_completed`, in which case the required modules must be completed, or `require_attempted`, in which case the required modules must have been attempted.
+
+The required modules are specified as a set of `tag`/`url_name`, joined by an ampersand.
+
### `customtag`
When we see ``, we will:
From c991c0489c14124343b8ffc4e9e39893bdb457b5 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Mon, 4 Feb 2013 10:26:25 -0500
Subject: [PATCH 050/132] Fix open ended test with flagging
---
lms/djangoapps/open_ended_grading/tests.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lms/djangoapps/open_ended_grading/tests.py b/lms/djangoapps/open_ended_grading/tests.py
index 57ea4f319c..131fe5ad9f 100644
--- a/lms/djangoapps/open_ended_grading/tests.py
+++ b/lms/djangoapps/open_ended_grading/tests.py
@@ -172,7 +172,8 @@ class TestPeerGradingService(ct.PageLoader):
'submission_key': 'fake key',
'score': '2',
'feedback': 'This is feedback',
- 'rubric_scores[]': [1, 2]}
+ 'rubric_scores[]': [1, 2],
+ 'submission_flagged' : False}
r = self.check_for_post_code(200, url, data)
d = json.loads(r.content)
self.assertTrue(d['success'])
From 8bd7b60aca4fa4ec7cde1f87fd117a8fa9b8a991 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Mon, 4 Feb 2013 10:50:07 -0500
Subject: [PATCH 051/132] Add in key to mock peer grading service
---
lms/djangoapps/open_ended_grading/peer_grading_service.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lms/djangoapps/open_ended_grading/peer_grading_service.py b/lms/djangoapps/open_ended_grading/peer_grading_service.py
index 8a649d9017..23e1488d9b 100644
--- a/lms/djangoapps/open_ended_grading/peer_grading_service.py
+++ b/lms/djangoapps/open_ended_grading/peer_grading_service.py
@@ -50,7 +50,7 @@ class MockPeerGradingService(object):
'max_score': 4})
def save_grade(self, location, grader_id, submission_id,
- score, feedback, submission_key, rubric_scores):
+ score, feedback, submission_key, rubric_scores, submission_flagged):
return json.dumps({'success': True})
def is_student_calibrated(self, problem_location, grader_id):
From 50eced5b323a253f0ff273aeac6aba66c5020970 Mon Sep 17 00:00:00 2001
From: Don Mitchell
Date: Mon, 4 Feb 2013 11:34:59 -0500
Subject: [PATCH 052/132] Attempt to patch the dnd hesitation fixes onto master
---
cms/static/js/views/overview.js | 119 ++--
cms/static/sass/_courseware.scss | 950 ++++++++++++++++---------------
2 files changed, 564 insertions(+), 505 deletions(-)
diff --git a/cms/static/js/views/overview.js b/cms/static/js/views/overview.js
index c007ef3efc..747033e625 100644
--- a/cms/static/js/views/overview.js
+++ b/cms/static/js/views/overview.js
@@ -8,7 +8,7 @@ $(document).ready(function() {
handle: '.drag-handle',
zIndex: 999,
start: initiateHesitate,
- drag: checkHoverState,
+ drag: generateCheckHoverState('.collapsed', ''), // '.unit'),
stop: removeHesitate,
revert: "invalid"
});
@@ -19,7 +19,7 @@ $(document).ready(function() {
handle: '.section-item .drag-handle',
zIndex: 999,
start: initiateHesitate,
- drag: checkHoverState,
+ drag: generateCheckHoverState('.courseware-section.collapsed', ''), //'.id-holder'),
stop: removeHesitate,
revert: "invalid"
});
@@ -56,64 +56,99 @@ $(document).ready(function() {
drop: onSectionReordered,
greedy: true
});
-
-});
+});
CMS.HesitateEvent.toggleXpandHesitation = null;
function initiateHesitate(event, ui) {
CMS.HesitateEvent.toggleXpandHesitation = new CMS.HesitateEvent(expandSection, 'dragLeave', true);
$('.collapsed').on('dragEnter', CMS.HesitateEvent.toggleXpandHesitation, CMS.HesitateEvent.toggleXpandHesitation.trigger);
- $('.collapsed').each(function() {
+ $('.collapsed, .unit, .id-holder').each(function() {
this.proportions = {width : this.offsetWidth, height : this.offsetHeight };
// reset b/c these were holding values from aborts
this.isover = false;
});
}
-function checkHoverState(event, ui) {
+
+function computeIntersection(droppable, uiHelper, y) {
+ /*
+ * Test whether y falls within the bounds of the droppable on the Y axis
+ */
+ // NOTE: this only judges y axis intersection b/c that's all we're doing right now
+ // don't expand the thing being carried
+ if (uiHelper.is(droppable)) {
+ return null;
+ }
+
+ $.extend(droppable, {offset : $(droppable).offset()});
+
+ var t = droppable.offset.top,
+ b = t + droppable.proportions.height;
+
+ if (t === b) {
+ // probably wrong values b/c invisible at the time of caching
+ droppable.proportions = { width : droppable.offsetWidth, height : droppable.offsetHeight };
+ b = t + droppable.proportions.height;
+ }
+ // equivalent to the intersects test
+ return (t < y && // Bottom Half
+ y < b ); // Top Half
+}
+
+function generateCheckHoverState(selectorsToOpen, selectorsToShove) {
+ return function(event, ui) {
// copied from jquery.ui.droppable.js $.ui.ddmanager.drag & other ui.intersect
var draggable = $(this).data("ui-draggable"),
- x1 = (draggable.positionAbs || draggable.position.absolute).left + (draggable.helperProportions.width / 2),
- y1 = (draggable.positionAbs || draggable.position.absolute).top + (draggable.helperProportions.height / 2);
- $('.collapsed').each(function() {
- // don't expand the thing being carried
- if (ui.helper.is(this)) {
- return;
- }
-
- $.extend(this, {offset : $(this).offset()});
+ centerY = (draggable.positionAbs || draggable.position.absolute).top + (draggable.helperProportions.height / 2);
+ $(selectorsToOpen).each(function() {
+ var intersects = computeIntersection(this, ui.helper, centerY),
+ c = !intersects && this.isover ? "isout" : (intersects && !this.isover ? "isover" : null);
- var droppable = this,
- l = droppable.offset.left,
- r = l + droppable.proportions.width,
- t = droppable.offset.top,
- b = t + droppable.proportions.height;
-
- if (l === r) {
- // probably wrong values b/c invisible at the time of caching
- droppable.proportions = { width : droppable.offsetWidth, height : droppable.offsetHeight };
- r = l + droppable.proportions.width;
- b = t + droppable.proportions.height;
- }
- // equivalent to the intersects test
- var intersects = (l < x1 && // Right Half
- x1 < r && // Left Half
- t < y1 && // Bottom Half
- y1 < b ), // Top Half
+ if(!c) {
+ return;
+ }
- c = !intersects && this.isover ? "isout" : (intersects && !this.isover ? "isover" : null);
-
- if(!c) {
- return;
- }
-
- this[c] = true;
- this[c === "isout" ? "isover" : "isout"] = false;
- $(this).trigger(c === "isover" ? "dragEnter" : "dragLeave");
+ this[c] = true;
+ this[c === "isout" ? "isover" : "isout"] = false;
+ $(this).trigger(c === "isover" ? "dragEnter" : "dragLeave");
});
+
+ $(selectorsToShove).each(function() {
+ var intersectsBottom = computeIntersection(this, ui.helper, (draggable.positionAbs || draggable.position.absolute).top);
+
+ if ($(this).hasClass('ui-dragging-pushup')) {
+ if (!intersectsBottom) {
+ console.log('not up', $(this).data('id'));
+ $(this).removeClass('ui-dragging-pushup');
+ }
+ }
+ else if (intersectsBottom) {
+ console.log('up', $(this).data('id'));
+ $(this).addClass('ui-dragging-pushup');
+ }
+
+ var intersectsTop = computeIntersection(this, ui.helper,
+ (draggable.positionAbs || draggable.position.absolute).top + draggable.helperProportions.height);
+
+ if ($(this).hasClass('ui-dragging-pushdown')) {
+ if (!intersectsTop) {
+ console.log('not down', $(this).data('id'));
+ $(this).removeClass('ui-dragging-pushdown');
+ }
+ }
+ else if (intersectsTop) {
+ console.log('down', $(this).data('id'));
+ $(this).addClass('ui-dragging-pushdown');
+ }
+
+ });
+ }
}
+
function removeHesitate(event, ui) {
$('.collapsed').off('dragEnter', CMS.HesitateEvent.toggleXpandHesitation.trigger);
+ $('.ui-dragging-pushdown').removeClass('ui-dragging-pushdown');
+ $('.ui-dragging-pushup').removeClass('ui-dragging-pushup');
CMS.HesitateEvent.toggleXpandHesitation = null;
}
@@ -189,3 +224,5 @@ function _handleReorder(event, ui, parentIdField, childrenSelector) {
});
}
+
+
diff --git a/cms/static/sass/_courseware.scss b/cms/static/sass/_courseware.scss
index 4ea110f4c8..dac3375811 100644
--- a/cms/static/sass/_courseware.scss
+++ b/cms/static/sass/_courseware.scss
@@ -1,90 +1,90 @@
input.courseware-unit-search-input {
- float: left;
- width: 260px;
- background-color: #fff;
+ float: left;
+ width: 260px;
+ background-color: #fff;
}
.branch {
- .section-item {
- @include clearfix();
+ .section-item {
+ @include clearfix();
- .details {
- display: block;
- float: left;
- margin-bottom: 0;
- width: 650px;
- }
+ .details {
+ display: block;
+ float: left;
+ margin-bottom: 0;
+ width: 650px;
+ }
- .gradable-status {
- float: right;
- position: relative;
- top: -4px;
- right: 50px;
- width: 145px;
+ .gradable-status {
+ float: right;
+ position: relative;
+ top: -4px;
+ right: 50px;
+ width: 145px;
- .status-label {
- position: absolute;
- top: 2px;
- right: -5px;
- display: none;
- width: 110px;
- padding: 5px 40px 5px 10px;
- @include border-radius(3px);
- color: $lightGrey;
- text-align: right;
- font-size: 12px;
- font-weight: bold;
- line-height: 16px;
- }
+ .status-label {
+ position: absolute;
+ top: 2px;
+ right: -5px;
+ display: none;
+ width: 110px;
+ padding: 5px 40px 5px 10px;
+ @include border-radius(3px);
+ color: $lightGrey;
+ text-align: right;
+ font-size: 12px;
+ font-weight: bold;
+ line-height: 16px;
+ }
- .menu-toggle {
- z-index: 10;
- position: absolute;
- top: 0;
- right: 5px;
- padding: 5px;
- color: $mediumGrey;
+ .menu-toggle {
+ z-index: 10;
+ position: absolute;
+ top: 0;
+ right: 5px;
+ padding: 5px;
+ color: $mediumGrey;
- &:hover, &.is-active {
- color: $blue;
- }
- }
+ &:hover, &.is-active {
+ color: $blue;
+ }
+ }
- .menu {
- z-index: 1;
- display: none;
- opacity: 0.0;
- position: absolute;
- top: -1px;
- left: 5px;
- margin: 0;
- padding: 8px 12px;
- background: $white;
- border: 1px solid $mediumGrey;
- font-size: 12px;
- @include border-radius(4px);
- @include box-shadow(0 1px 2px rgba(0, 0, 0, .2));
- @include transition(opacity .15s);
+ .menu {
+ z-index: 1;
+ display: none;
+ opacity: 0.0;
+ position: absolute;
+ top: -1px;
+ left: 5px;
+ margin: 0;
+ padding: 8px 12px;
+ background: $white;
+ border: 1px solid $mediumGrey;
+ font-size: 12px;
+ @include border-radius(4px);
+ @include box-shadow(0 1px 2px rgba(0, 0, 0, .2));
+ @include transition(opacity .15s);
- li {
- width: 115px;
- margin-bottom: 3px;
- padding-bottom: 3px;
- border-bottom: 1px solid $lightGrey;
+ li {
+ width: 115px;
+ margin-bottom: 3px;
+ padding-bottom: 3px;
+ border-bottom: 1px solid $lightGrey;
- &:last-child {
- margin-bottom: 0;
- padding-bottom: 0;
- border: none;
+ &:last-child {
+ margin-bottom: 0;
+ padding-bottom: 0;
+ border: none;
- a {
- color: $darkGrey;
- }
- }
- }
+ a {
+ color: $darkGrey;
+ }
+ }
+ }
a {
color: $blue;
@@ -127,262 +127,262 @@ input.courseware-unit-search-input {
.courseware-section {
- position: relative;
- background: #fff;
- border-radius: 3px;
- border: 1px solid $mediumGrey;
- margin-top: 15px;
- padding-bottom: 12px;
- @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.1));
+ position: relative;
+ background: #fff;
+ border-radius: 3px;
+ border: 1px solid $mediumGrey;
+ margin-top: 15px;
+ padding-bottom: 12px;
+ @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.1));
- &:first-child {
- margin-top: 0;
- }
+ &:first-child {
+ margin-top: 0;
+ }
- &.collapsed {
- padding-bottom: 0;
- }
+ &.collapsed {
+ padding-bottom: 0;
+ }
- label {
- float: left;
- line-height: 29px;
- }
+ label {
+ float: left;
+ line-height: 29px;
+ }
- .datepair {
- float: left;
- margin-left: 10px;
- }
+ .datepair {
+ float: left;
+ margin-left: 10px;
+ }
- .section-published-date {
- position: absolute;
- top: 19px;
- right: 90px;
- padding: 4px 10px;
- border-radius: 3px;
- background: $lightGrey;
- text-align: right;
+ .section-published-date {
+ position: absolute;
+ top: 19px;
+ right: 90px;
+ padding: 4px 10px;
+ border-radius: 3px;
+ background: $lightGrey;
+ text-align: right;
- .published-status {
- font-size: 12px;
- margin-right: 15px;
+ .published-status {
+ font-size: 12px;
+ margin-right: 15px;
- strong {
- font-weight: bold;
- }
- }
+ strong {
+ font-weight: bold;
+ }
+ }
- .schedule-button {
- @include blue-button;
- }
+ .schedule-button {
+ @include blue-button;
+ }
- .edit-button {
- @include blue-button;
- }
+ .edit-button {
+ @include blue-button;
+ }
- .schedule-button,
- .edit-button {
- font-size: 11px;
- padding: 3px 15px 5px;
- }
- }
+ .schedule-button,
+ .edit-button {
+ font-size: 11px;
+ padding: 3px 15px 5px;
+ }
+ }
- .datepair .date,
- .datepair .time {
- padding-left: 0;
- padding-right: 0;
- border: none;
- background: none;
- @include box-shadow(none);
- font-size: 13px;
- font-weight: bold;
- color: $blue;
- cursor: pointer;
- }
+ .datepair .date,
+ .datepair .time {
+ padding-left: 0;
+ padding-right: 0;
+ border: none;
+ background: none;
+ @include box-shadow(none);
+ font-size: 13px;
+ font-weight: bold;
+ color: $blue;
+ cursor: pointer;
+ }
- .datepair .date {
- width: 80px;
- }
+ .datepair .date {
+ width: 80px;
+ }
- .datepair .time {
- width: 65px;
- }
+ .datepair .time {
+ width: 65px;
+ }
- &.collapsed .subsection-list,
- .collapsed .subsection-list,
- .collapsed > ol {
- display: none !important;
- }
+ &.collapsed .subsection-list,
+ .collapsed .subsection-list,
+ .collapsed > ol {
+ display: none !important;
+ }
- header {
- min-height: 75px;
- @include clearfix();
+ header {
+ min-height: 75px;
+ @include clearfix();
- .item-details, .section-published-date {
+ .item-details, .section-published-date {
- }
+ }
- .item-details {
- display: inline-block;
- padding: 20px 0 10px 0;
- @include clearfix();
+ .item-details {
+ display: inline-block;
+ padding: 20px 0 10px 0;
+ @include clearfix();
- .section-name {
- float: left;
- margin-right: 10px;
- width: 350px;
- font-size: 19px;
- font-weight: bold;
- color: $blue;
- }
+ .section-name {
+ float: left;
+ margin-right: 10px;
+ width: 350px;
+ font-size: 19px;
+ font-weight: bold;
+ color: $blue;
+ }
- .section-name-span {
- cursor: pointer;
- @include transition(color .15s);
+ .section-name-span {
+ cursor: pointer;
+ @include transition(color .15s);
- &:hover {
- color: $orange;
- }
- }
+ &:hover {
+ color: $orange;
+ }
+ }
- .section-name-edit {
- position: relative;
- width: 400px;
- background: $white;
+ .section-name-edit {
+ position: relative;
+ width: 400px;
+ background: $white;
- input {
- font-size: 16px;
- }
-
- .save-button {
- @include blue-button;
- padding: 7px 20px 7px;
- margin-right: 5px;
- }
+ input {
+ font-size: 16px;
+ }
+
+ .save-button {
+ @include blue-button;
+ padding: 7px 20px 7px;
+ margin-right: 5px;
+ }
- .cancel-button {
- @include white-button;
- padding: 7px 20px 7px;
- }
- }
+ .cancel-button {
+ @include white-button;
+ padding: 7px 20px 7px;
+ }
+ }
- .section-published-date {
- float: right;
- width: 265px;
- margin-right: 220px;
- @include border-radius(3px);
- background: $lightGrey;
+ .section-published-date {
+ float: right;
+ width: 265px;
+ margin-right: 220px;
+ @include border-radius(3px);
+ background: $lightGrey;
- .published-status {
- font-size: 12px;
- margin-right: 15px;
+ .published-status {
+ font-size: 12px;
+ margin-right: 15px;
- strong {
- font-weight: bold;
- }
- }
+ strong {
+ font-weight: bold;
+ }
+ }
- .schedule-button {
- @include blue-button;
- }
+ .schedule-button {
+ @include blue-button;
+ }
- .edit-button {
- @include blue-button;
- }
+ .edit-button {
+ @include blue-button;
+ }
- .schedule-button,
- .edit-button {
- font-size: 11px;
- padding: 3px 15px 5px;
-
- }
- }
+ .schedule-button,
+ .edit-button {
+ font-size: 11px;
+ padding: 3px 15px 5px;
+
+ }
+ }
- .gradable-status {
- position: absolute;
- top: 20px;
- right: 70px;
- width: 145px;
+ .gradable-status {
+ position: absolute;
+ top: 20px;
+ right: 70px;
+ width: 145px;
- .status-label {
- position: absolute;
- top: 0;
- right: 2px;
- display: none;
- width: 100px;
- padding: 10px 35px 10px 10px;
- @include border-radius(3px);
- background: $lightGrey;
- color: $lightGrey;
- text-align: right;
- font-size: 12px;
- font-weight: bold;
- line-height: 16px;
- }
+ .status-label {
+ position: absolute;
+ top: 0;
+ right: 2px;
+ display: none;
+ width: 100px;
+ padding: 10px 35px 10px 10px;
+ @include border-radius(3px);
+ background: $lightGrey;
+ color: $lightGrey;
+ text-align: right;
+ font-size: 12px;
+ font-weight: bold;
+ line-height: 16px;
+ }
- .menu-toggle {
- z-index: 10;
- position: absolute;
- top: 2px;
- right: 5px;
- padding: 5px;
- color: $lightGrey;
+ .menu-toggle {
+ z-index: 10;
+ position: absolute;
+ top: 2px;
+ right: 5px;
+ padding: 5px;
+ color: $lightGrey;
- &:hover, &.is-active {
- color: $blue;
- }
- }
+ &:hover, &.is-active {
+ color: $blue;
+ }
+ }
- .menu {
- z-index: 1;
- display: none;
- opacity: 0.0;
- position: absolute;
- top: -1px;
- left: 2px;
- margin: 0;
- padding: 8px 12px;
- background: $white;
- border: 1px solid $mediumGrey;
- font-size: 12px;
- @include border-radius(4px);
- @include box-shadow(0 1px 2px rgba(0, 0, 0, .2));
- @include transition(opacity .15s);
- @include transition(display .15s);
+ .menu {
+ z-index: 1;
+ display: none;
+ opacity: 0.0;
+ position: absolute;
+ top: -1px;
+ left: 2px;
+ margin: 0;
+ padding: 8px 12px;
+ background: $white;
+ border: 1px solid $mediumGrey;
+ font-size: 12px;
+ @include border-radius(4px);
+ @include box-shadow(0 1px 2px rgba(0, 0, 0, .2));
+ @include transition(opacity .15s);
+ @include transition(display .15s);
- li {
- width: 115px;
- margin-bottom: 3px;
- padding-bottom: 3px;
- border-bottom: 1px solid $lightGrey;
+ li {
+ width: 115px;
+ margin-bottom: 3px;
+ padding-bottom: 3px;
+ border-bottom: 1px solid $lightGrey;
- &:last-child {
- margin-bottom: 0;
- padding-bottom: 0;
- border: none;
+ &:last-child {
+ margin-bottom: 0;
+ padding-bottom: 0;
+ border: none;
- a {
- color: $darkGrey;
- }
- }
- }
+ a {
+ color: $darkGrey;
+ }
+ }
+ }
- a {
+ a {
- &.is-selected {
- font-weight: bold;
- }
- }
- }
+ &.is-selected {
+ font-weight: bold;
+ }
+ }
+ }
- // dropdown state
- &.is-active {
+ // dropdown state
+ &.is-active {
- .menu {
- z-index: 1000;
- display: block;
- opacity: 1.0;
- }
+ .menu {
+ z-index: 1000;
+ display: block;
+ opacity: 1.0;
+ }
.menu-toggle {
@@ -408,256 +408,278 @@ input.courseware-unit-search-input {
}
}
- .item-actions {
- margin-top: 21px;
- margin-right: 12px;
+ .item-actions {
+ margin-top: 21px;
+ margin-right: 12px;
- .edit-button,
- .delete-button {
- margin-top: -3px;
- }
- }
+ .edit-button,
+ .delete-button {
+ margin-top: -3px;
+ }
+ }
- .expand-collapse-icon {
- float: left;
- margin: 29px 6px 16px 16px;
- @include transition(none);
+ .expand-collapse-icon {
+ float: left;
+ margin: 29px 6px 16px 16px;
+ @include transition(none);
- &.expand {
- background-position: 0 0;
- }
+ &.expand {
+ background-position: 0 0;
+ }
- &.collapsed {
-
- }
- }
+ &.collapsed {
+
+ }
+ }
- .drag-handle {
- margin-left: 11px;
- }
- }
+ .drag-handle {
+ margin-left: 11px;
+ }
+ }
- h3 {
- font-size: 19px;
- font-weight: 700;
- color: $blue;
- }
+ h3 {
+ font-size: 19px;
+ font-weight: 700;
+ color: $blue;
+ }
- .section-name-span {
- cursor: pointer;
- @include transition(color .15s);
+ .section-name-span {
+ cursor: pointer;
+ @include transition(color .15s);
- &:hover {
- color: $orange;
- }
- }
+ &:hover {
+ color: $orange;
+ }
+ }
- .section-name-form {
- margin-bottom: 15px;
- }
+ .section-name-form {
+ margin-bottom: 15px;
+ }
- .section-name-edit {
- input {
- font-size: 16px;
- }
-
- .save-button {
- @include blue-button;
- padding: 7px 20px 7px;
- margin-right: 5px;
- }
+ .section-name-edit {
+ input {
+ font-size: 16px;
+ }
+
+ .save-button {
+ @include blue-button;
+ padding: 7px 20px 7px;
+ margin-right: 5px;
+ }
- .cancel-button {
- @include white-button;
- padding: 7px 20px 7px;
- }
- }
+ .cancel-button {
+ @include white-button;
+ padding: 7px 20px 7px;
+ }
+ }
- h4 {
- font-size: 12px;
- color: #878e9d;
+ h4 {
+ font-size: 12px;
+ color: #878e9d;
- strong {
- font-weight: bold;
- }
- }
+ strong {
+ font-weight: bold;
+ }
+ }
- .list-header {
- @include linear-gradient(top, transparent, rgba(0, 0, 0, .1));
- background-color: #ced2db;
- border-radius: 3px 3px 0 0;
- }
+ .list-header {
+ @include linear-gradient(top, transparent, rgba(0, 0, 0, .1));
+ background-color: #ced2db;
+ border-radius: 3px 3px 0 0;
+ }
- .subsection-list {
- margin: 0 12px;
+ .subsection-list {
+ margin: 0 12px;
- > ol {
- @include tree-view;
- border-top-width: 0;
- }
- }
+ > ol {
+ @include tree-view;
+ border-top-width: 0;
+ }
+ }
- &.new-section {
- header {
- height: auto;
- @include clearfix();
- }
+ &.new-section {
+ header {
+ height: auto;
+ @include clearfix();
+ }
- .expand-collapse-icon {
- visibility: hidden;
- }
- }
+ .expand-collapse-icon {
+ visibility: hidden;
+ }
+ }
}
.toggle-button-sections {
- display: none;
- position: relative;
- float: right;
- margin-top: 10px;
+ display: none;
+ position: relative;
+ float: right;
+ margin-top: 10px;
- font-size: 13px;
- color: $darkGrey;
+ font-size: 13px;
+ color: $darkGrey;
- &.is-shown {
- display: block;
- }
+ &.is-shown {
+ display: block;
+ }
- .ss-icon {
- @include border-radius(20px);
- position: relative;
- top: -1px;
- display: inline-block;
- margin-right: 2px;
- line-height: 5px;
- font-size: 11px;
- }
+ .ss-icon {
+ @include border-radius(20px);
+ position: relative;
+ top: -1px;
+ display: inline-block;
+ margin-right: 2px;
+ line-height: 5px;
+ font-size: 11px;
+ }
- .label {
- display: inline-block;
- }
+ .label {
+ display: inline-block;
+ }
}
.new-section-name,
.new-subsection-name-input {
- width: 515px;
+ width: 515px;
}
.new-section-name-save,
.new-subsection-name-save {
- @include blue-button;
- padding: 4px 20px 7px;
- margin: 0 5px;
- color: #fff !important;
+ @include blue-button;
+ padding: 4px 20px 7px;
+ margin: 0 5px;
+ color: #fff !important;
}
.new-section-name-cancel,
.new-subsection-name-cancel {
- @include white-button;
- padding: 4px 20px 7px;
- color: #8891a1 !important;
+ @include white-button;
+ padding: 4px 20px 7px;
+ color: #8891a1 !important;
}
.dummy-calendar {
- display: none;
- position: absolute;
- top: 55px;
- left: 110px;
- z-index: 9999;
- border: 1px solid #3C3C3C;
- @include box-shadow(0 1px 15px rgba(0, 0, 0, .2));
+ display: none;
+ position: absolute;
+ top: 55px;
+ left: 110px;
+ z-index: 9999;
+ border: 1px solid #3C3C3C;
+ @include box-shadow(0 1px 15px rgba(0, 0, 0, .2));
}
.unit-name-input {
- padding: 20px 40px;
+ padding: 20px 40px;
- label {
- display: block;
- }
+ label {
+ display: block;
+ }
- input {
- width: 100%;
- font-size: 20px;
- }
+ input {
+ width: 100%;
+ font-size: 20px;
+ }
}
.preview {
- background: url(../img/preview.jpg) center top no-repeat;
+ background: url(../img/preview.jpg) center top no-repeat;
}
.edit-subsection-publish-settings {
- display: none;
- position: fixed;
- top: 100px;
- left: 50%;
- z-index: 99999;
- width: 600px;
- margin-left: -300px;
- background: #fff;
- text-align: center;
+ display: none;
+ position: fixed;
+ top: 100px;
+ left: 50%;
+ z-index: 99999;
+ width: 600px;
+ margin-left: -300px;
+ background: #fff;
+ text-align: center;
- .settings {
- padding: 40px;
- }
+ .settings {
+ padding: 40px;
+ }
- h3 {
- font-size: 34px;
- font-weight: 300;
- }
+ h3 {
+ font-size: 34px;
+ font-weight: 300;
+ }
- .picker {
- margin: 30px 0 65px;
- }
+ .picker {
+ margin: 30px 0 65px;
+ }
- .description {
- margin-top: 30px;
- font-size: 14px;
- line-height: 20px;
- }
+ .description {
+ margin-top: 30px;
+ font-size: 14px;
+ line-height: 20px;
+ }
- strong {
- font-weight: 700;
- }
+ strong {
+ font-weight: 700;
+ }
- .start-date,
- .start-time {
- font-size: 19px;
- }
+ .start-date,
+ .start-time {
+ font-size: 19px;
+ }
- .save-button {
- @include blue-button;
- margin-right: 10px;
- }
+ .save-button {
+ @include blue-button;
+ margin-right: 10px;
+ }
- .cancel-button {
- @include white-button;
- }
+ .cancel-button {
+ @include white-button;
+ }
- .save-button,
- .cancel-button {
- font-size: 16px;
- }
+ .save-button,
+ .cancel-button {
+ font-size: 16px;
+ }
}
.collapse-all-button {
- float: right;
- margin-top: 10px;
- font-size: 13px;
- color: $darkGrey;
+ float: right;
+ margin-top: 10px;
+ font-size: 13px;
+ color: $darkGrey;
}
// sort/drag and drop
.ui-droppable {
- min-height: 20px;
+ @include transition (padding 0.5s ease-in-out 0s);
+ min-height: 20px;
+ padding: 0;
- &.dropover {
- padding-top: 10px;
- padding-bottom: 10px;
- }
+ &.dropover {
+ padding: 15px 0;
+ }
+}
+
+// sort/drag and drop - make room
+.ui-dragging-pushdown {
+ @include transition (margin-top 0.5s ease-in-out 0s);
+ margin-top: 15px;
+}
+
+.ui-dragging-pushup {
+ @include transition (margin-bottom 0.5s ease-in-out 0s);
+ margin-bottom: 15px;
+}
+
+.ui-draggable-dragging {
+ @include box-shadow(0 1px 2px rgba(0, 0, 0, .3));
+ border: 1px solid $darkGrey;
+ opacity : 0.2;
+ &:hover {
+ opacity : 1.0;
+ .section-item {
+ background: $yellow !important;
+ }
+ }
}
ol.ui-droppable .branch:first-child .section-item {
- border-top: none;
+ border-top: none;
}
-
-
From 7c39e385393c083dee2dc5bf42c1f5ea91b0db87 Mon Sep 17 00:00:00 2001
From: John Jarvis
Date: Mon, 4 Feb 2013 12:05:45 -0500
Subject: [PATCH 053/132] Defaulting service_variant to a blank string
---
common/lib/logsettings.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/common/lib/logsettings.py b/common/lib/logsettings.py
index 6850941db7..8fc2bb9db1 100644
--- a/common/lib/logsettings.py
+++ b/common/lib/logsettings.py
@@ -40,6 +40,11 @@ def get_logger_config(log_dir,
if console_loglevel is None or console_loglevel not in LOG_LEVELS:
console_loglevel = 'DEBUG' if debug else 'INFO'
+ if service_variant is None:
+ # default to a blank string so that if SERVICE_VARIANT is not
+ # set we will not log to a sub directory
+ service_variant = ''
+
hostname = platform.node().split(".")[0]
syslog_format = ("[service_variant={service_variant}]"
"[%(name)s][env:{logging_env}] %(levelname)s "
From 902cdefefa8a698ba733d62127d2aa1520e91148 Mon Sep 17 00:00:00 2001
From: Don Mitchell
Date: Mon, 4 Feb 2013 12:05:49 -0500
Subject: [PATCH 054/132] Added comments for unused 2nd arg on
generateCheckHoverState
---
cms/static/js/views/overview.js | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/cms/static/js/views/overview.js b/cms/static/js/views/overview.js
index 747033e625..8cbae177a8 100644
--- a/cms/static/js/views/overview.js
+++ b/cms/static/js/views/overview.js
@@ -8,7 +8,9 @@ $(document).ready(function() {
handle: '.drag-handle',
zIndex: 999,
start: initiateHesitate,
- drag: generateCheckHoverState('.collapsed', ''), // '.unit'),
+ // left 2nd arg in as inert selector b/c i was uncertain whether we'd try to get the shove up/down
+ // to work in the future
+ drag: generateCheckHoverState('.collapsed', ''),
stop: removeHesitate,
revert: "invalid"
});
@@ -19,7 +21,7 @@ $(document).ready(function() {
handle: '.section-item .drag-handle',
zIndex: 999,
start: initiateHesitate,
- drag: generateCheckHoverState('.courseware-section.collapsed', ''), //'.id-holder'),
+ drag: generateCheckHoverState('.courseware-section.collapsed', ''),
stop: removeHesitate,
revert: "invalid"
});
@@ -95,6 +97,7 @@ function computeIntersection(droppable, uiHelper, y) {
y < b ); // Top Half
}
+// NOTE: selectorsToShove is not currently being used but I left this code as it did work but not well
function generateCheckHoverState(selectorsToOpen, selectorsToShove) {
return function(event, ui) {
// copied from jquery.ui.droppable.js $.ui.ddmanager.drag & other ui.intersect
From f742b7d865cbe9f96aedcc635721781180c5938d Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Mon, 4 Feb 2013 12:07:03 -0500
Subject: [PATCH 055/132] Start to integrate peer grading xmodule back into
notifications
---
.../xmodule/combined_open_ended_rubric.py | 4 +-
.../open_ended_notifications.py | 2 +-
lms/djangoapps/open_ended_grading/views.py | 67 +++++++++----------
lms/urls.py | 3 +
4 files changed, 40 insertions(+), 36 deletions(-)
diff --git a/common/lib/xmodule/xmodule/combined_open_ended_rubric.py b/common/lib/xmodule/xmodule/combined_open_ended_rubric.py
index 3e3d8e67f2..6f6752f221 100644
--- a/common/lib/xmodule/xmodule/combined_open_ended_rubric.py
+++ b/common/lib/xmodule/xmodule/combined_open_ended_rubric.py
@@ -33,7 +33,9 @@ class CombinedOpenEndedRubric(object):
'view_only': self.view_only})
success = True
except:
- raise RubricParsingError("[render_rubric] Could not parse the rubric with xml: {0}".format(rubric_xml))
+ error_message = "[render_rubric] Could not parse the rubric with xml: {0}".format(rubric_xml)
+ log.error(error_message)
+ raise RubricParsingError(error_message)
return success, html
def extract_categories(self, element):
diff --git a/lms/djangoapps/open_ended_grading/open_ended_notifications.py b/lms/djangoapps/open_ended_grading/open_ended_notifications.py
index fec893894f..26f7339291 100644
--- a/lms/djangoapps/open_ended_grading/open_ended_notifications.py
+++ b/lms/djangoapps/open_ended_grading/open_ended_notifications.py
@@ -1,6 +1,5 @@
from django.conf import settings
from staff_grading_service import StaffGradingService
-from peer_grading_service import PeerGradingService
from open_ended_grading.controller_query_service import ControllerQueryService
import json
from student.models import unique_id_for_user
@@ -10,6 +9,7 @@ import logging
from courseware.access import has_access
from util.cache import cache
import datetime
+from xmodule import peer_grading_service
log=logging.getLogger(__name__)
diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py
index 1777f26e2e..4e10e7de96 100644
--- a/lms/djangoapps/open_ended_grading/views.py
+++ b/lms/djangoapps/open_ended_grading/views.py
@@ -2,6 +2,7 @@
import logging
import urllib
+import re
from django.conf import settings
from django.views.decorators.cache import cache_control
@@ -24,6 +25,10 @@ import open_ended_notifications
from xmodule.modulestore.django import modulestore
from xmodule.modulestore import search
+from xmodule import peer_grading_module
+from xmodule import peer_grading_service
+from mitxmako.shortcuts import render_to_string
+from xmodule.x_module import ModuleSystem
from django.http import HttpResponse, Http404
@@ -87,41 +92,35 @@ def peer_grading(request, course_id):
'''
Show a peer grading interface
'''
+
+ ajax_url = ajax_url = _reverse_with_slash('peer_grading', course_id)
+ track_function = None
+ get_module = None
+ render_template = render_to_string
+ replace_urls = None
+ anonymous_student_id= unique_id_for_user(request.user)
+
+ system = ModuleSystem(
+ ajax_url,
+ track_function,
+ get_module,
+ render_template,
+ replace_urls,
+ course_id = course_id,
+ anonymous_student_id = anonymous_student_id
+ )
+
+ location = ""
+ definition = ""
+ descriptor = peer_grading_module.PeerGradingDescriptor
+ instance_state = {}
+ pg_url = re.sub("/courses", "i4x://", ajax_url)
+
+ pg_module = peer_grading_module.PeerGradingModule(system, pg_url, definition, descriptor, instance_state)
+
course = get_course_with_access(request.user, course_id, 'load')
- # call problem list service
- success = False
- error_text = ""
- problem_list = []
- try:
- problem_list_json = peer_gs.get_problem_list(course_id, unique_id_for_user(request.user))
- problem_list_dict = json.loads(problem_list_json)
- success = problem_list_dict['success']
- if 'error' in problem_list_dict:
- error_text = problem_list_dict['error']
-
- problem_list = problem_list_dict['problem_list']
-
- except GradingServiceError:
- error_text = "Error occured while contacting the grading service"
- success = False
- # catch error if if the json loads fails
- except ValueError:
- error_text = "Could not get problem list"
- success = False
-
- ajax_url = _reverse_with_slash('peer_grading', course_id)
-
- return render_to_response('peer_grading/peer_grading.html', {
- 'course': course,
- 'course_id': course_id,
- 'ajax_url': ajax_url,
- 'success': success,
- 'problem_list': problem_list,
- 'error_text': error_text,
- # Checked above
- 'staff_access': False, })
-
+ return pg_module.get_html()
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def peer_grading_problem(request, course_id):
@@ -317,7 +316,7 @@ def take_action_on_flags(request, course_id):
response = controller_qs.take_action_on_flags(course_id, student_id, submission_id, action_type)
return HttpResponse(response, mimetype="application/json")
except GradingServiceError:
- log.exception("Error saving calibration grade, location: {0}, submission_id: {1}, submission_key: {2}, grader_id: {3}".format(location, submission_id, submission_key, grader_id))
+ log.exception("Error saving calibration grade, submission_id: {0}, submission_key: {1}, grader_id: {2}".format(submission_id, submission_key, grader_id))
return _err_response('Could not connect to grading service')
diff --git a/lms/urls.py b/lms/urls.py
index 6e8d08e256..36b618e454 100644
--- a/lms/urls.py
+++ b/lms/urls.py
@@ -297,6 +297,9 @@ if settings.COURSEWARE_ENABLED:
# Open Ended Notifications
url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/open_ended_notifications$',
'open_ended_grading.views.combined_notifications', name='open_ended_notifications'),
+
+ url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/peer_grading$',
+ 'open_ended_grading.views.peer_grading', name='peer_grading'),
)
# discussion forums live within courseware, so courseware must be enabled first
From 7263cbfcca28064faf3fff83a8347995ed69562c Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Mon, 4 Feb 2013 12:41:14 -0500
Subject: [PATCH 056/132] Fixes to make peer grading show up in notifications
---
lms/djangoapps/open_ended_grading/views.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py
index 4e10e7de96..c28b4f5be6 100644
--- a/lms/djangoapps/open_ended_grading/views.py
+++ b/lms/djangoapps/open_ended_grading/views.py
@@ -113,8 +113,8 @@ def peer_grading(request, course_id):
location = ""
definition = ""
descriptor = peer_grading_module.PeerGradingDescriptor
- instance_state = {}
- pg_url = re.sub("/courses", "i4x://", ajax_url)
+ instance_state = None
+ pg_url = re.sub("/courses", "i4x:/", ajax_url)[:-1]
pg_module = peer_grading_module.PeerGradingModule(system, pg_url, definition, descriptor, instance_state)
From c6c89e4216f94b88031c58b9d17ce220138bdf41 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Mon, 4 Feb 2013 13:16:05 -0500
Subject: [PATCH 057/132] Make peer grading xmodule work with notifications
---
common/djangoapps/xmodule_modifiers.py | 1 -
lms/djangoapps/open_ended_grading/views.py | 36 +++++++++++++++----
lms/envs/dev.py | 20 +++++++++--
.../peer_grading_notifications.html | 17 +++++++++
lms/urls.py | 2 ++
5 files changed, 65 insertions(+), 11 deletions(-)
create mode 100644 lms/templates/peer_grading/peer_grading_notifications.html
diff --git a/common/djangoapps/xmodule_modifiers.py b/common/djangoapps/xmodule_modifiers.py
index 5c19a2f1d7..3fad5d0b37 100644
--- a/common/djangoapps/xmodule_modifiers.py
+++ b/common/djangoapps/xmodule_modifiers.py
@@ -64,7 +64,6 @@ def replace_static_urls(get_html, prefix, course_namespace=None):
return replace_urls(get_html(), staticfiles_prefix=prefix, course_namespace = course_namespace)
return _get_html
-
def grade_histogram(module_id):
''' Print out a histogram of grades on a given problem.
Part of staff member debug info.
diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py
index c28b4f5be6..671fa1ee63 100644
--- a/lms/djangoapps/open_ended_grading/views.py
+++ b/lms/djangoapps/open_ended_grading/views.py
@@ -29,6 +29,9 @@ from xmodule import peer_grading_module
from xmodule import peer_grading_service
from mitxmako.shortcuts import render_to_string
from xmodule.x_module import ModuleSystem
+from courseware import module_render
+from xmodule.modulestore.django import modulestore
+from courseware.models import StudentModule, StudentModuleCache
from django.http import HttpResponse, Http404
@@ -86,14 +89,14 @@ def staff_grading(request, course_id):
# Checked above
'staff_access': True, })
-
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def peer_grading(request, course_id):
'''
Show a peer grading interface
'''
+ course = get_course_with_access(request.user, course_id, 'load')
- ajax_url = ajax_url = _reverse_with_slash('peer_grading', course_id)
+ ajax_url = _reverse_with_slash('peer_grading_ajax', course_id)
track_function = None
get_module = None
render_template = render_to_string
@@ -114,16 +117,35 @@ def peer_grading(request, course_id):
definition = ""
descriptor = peer_grading_module.PeerGradingDescriptor
instance_state = None
- pg_url = re.sub("/courses", "i4x:/", ajax_url)[:-1]
- pg_module = peer_grading_module.PeerGradingModule(system, pg_url, definition, descriptor, instance_state)
+ pg_ajax = _reverse_with_slash('peer_grading', course_id)
+ pg_url = re.sub("/courses", "i4x:/",pg_ajax)[:-1]
+ pg_location = request.GET.get('location', pg_url)
- course = get_course_with_access(request.user, course_id, 'load')
+ pg_module = peer_grading_module.PeerGradingModule(system, pg_location, definition, descriptor, instance_state)
- return pg_module.get_html()
+ """
+ return_html = pg_module.get_html()
+ log.debug(return_html)
+ response = render_to_response('peer_grading/peer_grading_notifications.html', {
+ 'peer_grading_html' : return_html,
+ 'course': course,
+ 'problem_location': pg_location,
+ 'course_id': course_id,
+ 'ajax_url': ajax_url,
+ 'staff_access': False,
+ })
+ """
+
+ student_module_cache = StudentModuleCache(course_id,
+ request.user, descriptor)
+
+ pg_xmodule = module_render.get_module(request.user, request, pg_location, student_module_cache, course_id)
+
+ return pg_xmodule.get_html()
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
-def peer_grading_problem(request, course_id):
+def peer_grading_ajax(request, course_id):
'''
Show individual problem interface
'''
diff --git a/lms/envs/dev.py b/lms/envs/dev.py
index 99ee9662ee..9429feb34f 100644
--- a/lms/envs/dev.py
+++ b/lms/envs/dev.py
@@ -52,20 +52,28 @@ CACHES = {
# We set it to be a DummyCache to force reloading of course.xml in dev.
# In staging environments, we would grab VERSION from data uploaded by the
# push process.
+ #'general': {
+ # 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
+ # 'KEY_PREFIX': 'general',
+ # 'VERSION': 4,
+ # 'LOCATION': 'mitx_loc_mem_cache_general',
+ # 'KEY_FUNCTION': 'util.memcache.safe_key',
+ #}
+
'general': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
'KEY_PREFIX': 'general',
'VERSION': 4,
'KEY_FUNCTION': 'util.memcache.safe_key',
- }
+ }
}
XQUEUE_INTERFACE = {
- "url": "https://sandbox-xqueue.edx.org",
+ "url": "http://127.0.0.1:3032",
"django_auth": {
"username": "lms",
- "password": "***REMOVED***"
+ "password": "abcd"
},
"basic_auth": ('anant', 'agarwal'),
}
@@ -198,3 +206,9 @@ PIPELINE_SASS_ARGUMENTS = '-r {proj_dir}/static/sass/bourbon/lib/bourbon.rb'.for
MITX_FEATURES['ENABLE_PEARSON_HACK_TEST'] = True
PEARSON_TEST_USER = "pearsontest"
PEARSON_TEST_PASSWORD = "12345"
+
+#AWS upload stuff for local file testing
+AWS_ACCESS_KEY_ID = "***REMOVED***"
+AWS_SECRET_ACCESS_KEY = "***REMOVED***"
+AWS_STORAGE_BUCKET_NAME = 'edxuploads'
+
diff --git a/lms/templates/peer_grading/peer_grading_notifications.html b/lms/templates/peer_grading/peer_grading_notifications.html
new file mode 100644
index 0000000000..40cf85fb0f
--- /dev/null
+++ b/lms/templates/peer_grading/peer_grading_notifications.html
@@ -0,0 +1,17 @@
+<%inherit file="/main.html" />
+<%block name="bodyclass">${course.css_class}%block>
+<%namespace name='static' file='/static_content.html'/>
+
+<%block name="headextra">
+<%static:css group='course'/>
+%block>
+
+<%block name="title">${course.number} Peer Grading%block>
+
+<%include file="/courseware/course_navigation.html" args="active_page='peer_grading'" />
+
+<%block name="js_extra">
+<%static:js group='peer_grading'/>
+%block>
+
+${peer_grading_html|n}
\ No newline at end of file
diff --git a/lms/urls.py b/lms/urls.py
index 36b618e454..02f3cbb03e 100644
--- a/lms/urls.py
+++ b/lms/urls.py
@@ -300,6 +300,8 @@ if settings.COURSEWARE_ENABLED:
url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/peer_grading$',
'open_ended_grading.views.peer_grading', name='peer_grading'),
+ url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/peer_grading_ajax$',
+ 'open_ended_grading.views.peer_grading_ajax', name='peer_grading_ajax'),
)
# discussion forums live within courseware, so courseware must be enabled first
From 39479d9e182121c2b9b2ceeb535ee6748d6c5cd2 Mon Sep 17 00:00:00 2001
From: cahrens
Date: Mon, 4 Feb 2013 13:28:34 -0500
Subject: [PATCH 058/132] See if this helps with the tab-changing race
condition.
---
common/lib/xmodule/xmodule/js/src/html/edit.coffee | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/common/lib/xmodule/xmodule/js/src/html/edit.coffee b/common/lib/xmodule/xmodule/js/src/html/edit.coffee
index fa83343d7a..ccd7943917 100644
--- a/common/lib/xmodule/xmodule/js/src/html/edit.coffee
+++ b/common/lib/xmodule/xmodule/js/src/html/edit.coffee
@@ -71,15 +71,16 @@ class @HTMLEditingDescriptor
e.preventDefault();
if not $(e.currentTarget).hasClass('current')
- $('.editor-tabs .current', @element).removeClass('current')
$(e.currentTarget).addClass('current')
$('table.mceToolbar', @element).toggleClass(HTMLEditingDescriptor.isInactiveClass)
$(@advanced_editor.getWrapperElement()).toggleClass(HTMLEditingDescriptor.isInactiveClass)
visualEditor = @getVisualEditor()
if $(e.currentTarget).attr('data-tab') is 'visual'
+ $('.html-tab', @element).removeClass('current')
@showVisualEditor(visualEditor)
else
+ $('.visual-tab', @element).removeClass('current')
@showAdvancedEditor(visualEditor)
# Show the Advanced (codemirror) Editor. Pulled out as a helper method for unit testing.
From 3380745692d63b22287b8337711d8a2690fbcc07 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Mon, 4 Feb 2013 13:54:27 -0500
Subject: [PATCH 059/132] About to trim some lines
---
.../lib/xmodule/xmodule/peer_grading_module.py | 1 +
lms/djangoapps/courseware/module_render.py | 15 ++++++++-------
lms/djangoapps/open_ended_grading/views.py | 18 ++++++++++--------
3 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/common/lib/xmodule/xmodule/peer_grading_module.py b/common/lib/xmodule/xmodule/peer_grading_module.py
index c2df24dfff..0fcdaef68a 100644
--- a/common/lib/xmodule/xmodule/peer_grading_module.py
+++ b/common/lib/xmodule/xmodule/peer_grading_module.py
@@ -65,6 +65,7 @@ class PeerGradingModule(XModule):
#We need to set the location here so the child modules can use it
system.set('location', location)
+ log.debug("Location: {0}".format(location))
self.system = system
self.peer_gs = peer_grading_service(self.system)
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index 7ed32c8597..f6c193d9e4 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -140,12 +140,13 @@ def get_module(user, request, location, student_module_cache, course_id,
module. If there's an error, will try to return an instance of ErrorModule
if possible. If not possible, return None.
"""
- try:
- location = Location(location)
- descriptor = modulestore().get_instance(course_id, location, depth=depth)
- return get_module_for_descriptor(user, request, descriptor, student_module_cache, course_id,
- position=position, not_found_ok=not_found_ok,
- wrap_xmodule_display=wrap_xmodule_display)
+ #try:
+ location = Location(location)
+ descriptor = modulestore().get_instance(course_id, location, depth=depth)
+ return get_module_for_descriptor(user, request, descriptor, student_module_cache, course_id,
+ position=position, not_found_ok=not_found_ok,
+ wrap_xmodule_display=wrap_xmodule_display)
+ """
except ItemNotFoundError:
if not not_found_ok:
log.exception("Error in get_module")
@@ -154,7 +155,7 @@ def get_module(user, request, location, student_module_cache, course_id,
# Something has gone terribly wrong, but still not letting it turn into a 500.
log.exception("Error in get_module")
return None
-
+ """
def get_module_for_descriptor(user, request, descriptor, student_module_cache, course_id,
position=None, not_found_ok=False, wrap_xmodule_display=True):
diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py
index 671fa1ee63..2a5f7614cf 100644
--- a/lms/djangoapps/open_ended_grading/views.py
+++ b/lms/djangoapps/open_ended_grading/views.py
@@ -30,6 +30,7 @@ from xmodule import peer_grading_service
from mitxmako.shortcuts import render_to_string
from xmodule.x_module import ModuleSystem
from courseware import module_render
+from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
from courseware.models import StudentModule, StudentModuleCache
@@ -103,6 +104,11 @@ def peer_grading(request, course_id):
replace_urls = None
anonymous_student_id= unique_id_for_user(request.user)
+ pg_ajax = _reverse_with_slash('peer_grading', course_id)
+ pg_url = re.sub("/courses", "i4x:/",pg_ajax)[:-1]
+ pg_location = request.GET.get('location', pg_url)
+ pg_location = "i4x://MITx/oe101x/peergrading/init"
+
system = ModuleSystem(
ajax_url,
track_function,
@@ -113,14 +119,10 @@ def peer_grading(request, course_id):
anonymous_student_id = anonymous_student_id
)
- location = ""
definition = ""
- descriptor = peer_grading_module.PeerGradingDescriptor
instance_state = None
- pg_ajax = _reverse_with_slash('peer_grading', course_id)
- pg_url = re.sub("/courses", "i4x:/",pg_ajax)[:-1]
- pg_location = request.GET.get('location', pg_url)
+ descriptor = peer_grading_module.PeerGradingDescriptor(system)
pg_module = peer_grading_module.PeerGradingModule(system, pg_location, definition, descriptor, instance_state)
@@ -138,11 +140,11 @@ def peer_grading(request, course_id):
"""
student_module_cache = StudentModuleCache(course_id,
- request.user, descriptor)
+ request.user, [descriptor])
- pg_xmodule = module_render.get_module(request.user, request, pg_location, student_module_cache, course_id)
+ pg_xmodule = module_render.get_module(request.user, request, Location(pg_location), student_module_cache, course_id)
- return pg_xmodule.get_html()
+ return HttpResponse(pg_xmodule.get_html())
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def peer_grading_ajax(request, course_id):
From 14676d1c320746803d081034f7ce468b27969598 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Mon, 4 Feb 2013 14:05:02 -0500
Subject: [PATCH 060/132] Untrack dev.py, add in redirect code for peer grading
---
lms/djangoapps/open_ended_grading/views.py | 81 ++++------------------
lms/urls.py | 2 -
2 files changed, 14 insertions(+), 69 deletions(-)
diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py
index 2a5f7614cf..c41b3fa9dd 100644
--- a/lms/djangoapps/open_ended_grading/views.py
+++ b/lms/djangoapps/open_ended_grading/views.py
@@ -34,7 +34,7 @@ from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
from courseware.models import StudentModule, StudentModuleCache
-from django.http import HttpResponse, Http404
+from django.http import HttpResponse, Http404, HttpResponseRedirect
log = logging.getLogger(__name__)
@@ -95,75 +95,22 @@ def peer_grading(request, course_id):
'''
Show a peer grading interface
'''
+
course = get_course_with_access(request.user, course_id, 'load')
+ log.debug(course_id)
+ pg_location = "i4x://" + "MITx/oe101x/" + "peergrading/init"
- ajax_url = _reverse_with_slash('peer_grading_ajax', course_id)
- track_function = None
- get_module = None
- render_template = render_to_string
- replace_urls = None
- anonymous_student_id= unique_id_for_user(request.user)
+ base_course_url = reverse('courses')
+ problem_url_parts = search.path_to_location(modulestore(), course.id, pg_location)
+ problem_url = base_course_url + "/"
+ for z in xrange(0,len(problem_url_parts)):
+ part = problem_url_parts[z]
+ if part is not None:
+ if z==1:
+ problem_url += "courseware/"
+ problem_url += part + "/"
- pg_ajax = _reverse_with_slash('peer_grading', course_id)
- pg_url = re.sub("/courses", "i4x:/",pg_ajax)[:-1]
- pg_location = request.GET.get('location', pg_url)
- pg_location = "i4x://MITx/oe101x/peergrading/init"
-
- system = ModuleSystem(
- ajax_url,
- track_function,
- get_module,
- render_template,
- replace_urls,
- course_id = course_id,
- anonymous_student_id = anonymous_student_id
- )
-
- definition = ""
- instance_state = None
-
- descriptor = peer_grading_module.PeerGradingDescriptor(system)
-
- pg_module = peer_grading_module.PeerGradingModule(system, pg_location, definition, descriptor, instance_state)
-
- """
- return_html = pg_module.get_html()
- log.debug(return_html)
- response = render_to_response('peer_grading/peer_grading_notifications.html', {
- 'peer_grading_html' : return_html,
- 'course': course,
- 'problem_location': pg_location,
- 'course_id': course_id,
- 'ajax_url': ajax_url,
- 'staff_access': False,
- })
- """
-
- student_module_cache = StudentModuleCache(course_id,
- request.user, [descriptor])
-
- pg_xmodule = module_render.get_module(request.user, request, Location(pg_location), student_module_cache, course_id)
-
- return HttpResponse(pg_xmodule.get_html())
-
-@cache_control(no_cache=True, no_store=True, must_revalidate=True)
-def peer_grading_ajax(request, course_id):
- '''
- Show individual problem interface
- '''
- course = get_course_with_access(request.user, course_id, 'load')
- problem_location = request.GET.get("location")
-
- ajax_url = _reverse_with_slash('peer_grading', course_id)
-
- return render_to_response('peer_grading/peer_grading_problem.html', {
- 'view_html': '',
- 'course': course,
- 'problem_location': problem_location,
- 'course_id': course_id,
- 'ajax_url': ajax_url,
- # Checked above
- 'staff_access': False, })
+ return HttpResponseRedirect(problem_url)
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def student_problem_list(request, course_id):
diff --git a/lms/urls.py b/lms/urls.py
index 02f3cbb03e..36b618e454 100644
--- a/lms/urls.py
+++ b/lms/urls.py
@@ -300,8 +300,6 @@ if settings.COURSEWARE_ENABLED:
url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/peer_grading$',
'open_ended_grading.views.peer_grading', name='peer_grading'),
- url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/peer_grading_ajax$',
- 'open_ended_grading.views.peer_grading_ajax', name='peer_grading_ajax'),
)
# discussion forums live within courseware, so courseware must be enabled first
From 43ddf1fbcfd1e9a0fdab2cf33fd9aed18663f690 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Mon, 4 Feb 2013 14:14:15 -0500
Subject: [PATCH 061/132] Proper redirect behavior
---
lms/djangoapps/open_ended_grading/views.py | 6 +-
lms/envs/dev.py | 214 ---------------------
2 files changed, 4 insertions(+), 216 deletions(-)
delete mode 100644 lms/envs/dev.py
diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py
index c41b3fa9dd..c20ff85ee0 100644
--- a/lms/djangoapps/open_ended_grading/views.py
+++ b/lms/djangoapps/open_ended_grading/views.py
@@ -97,8 +97,10 @@ def peer_grading(request, course_id):
'''
course = get_course_with_access(request.user, course_id, 'load')
- log.debug(course_id)
- pg_location = "i4x://" + "MITx/oe101x/" + "peergrading/init"
+ course_id_parts = course.id.split("/")
+ course_id_norun = "/".join(course_id_parts[0:2])
+ pg_location = "i4x://" + course_id_norun + "/peergrading/init"
+ log.debug("PG LOCATION :{0}".format(pg_location))
base_course_url = reverse('courses')
problem_url_parts = search.path_to_location(modulestore(), course.id, pg_location)
diff --git a/lms/envs/dev.py b/lms/envs/dev.py
deleted file mode 100644
index 9429feb34f..0000000000
--- a/lms/envs/dev.py
+++ /dev/null
@@ -1,214 +0,0 @@
-"""
-This config file runs the simplest dev environment using sqlite, and db-based
-sessions. Assumes structure:
-
-/envroot/
- /db # This is where it'll write the database file
- /mitx # The location of this repo
- /log # Where we're going to write log files
-"""
-from .common import *
-from logsettings import get_logger_config
-
-DEBUG = True
-TEMPLATE_DEBUG = True
-
-
-MITX_FEATURES['DISABLE_START_DATES'] = True
-MITX_FEATURES['ENABLE_SQL_TRACKING_LOGS'] = True
-MITX_FEATURES['SUBDOMAIN_COURSE_LISTINGS'] = False # Enable to test subdomains--otherwise, want all courses to show up
-MITX_FEATURES['SUBDOMAIN_BRANDING'] = True
-MITX_FEATURES['FORCE_UNIVERSITY_DOMAIN'] = None # show all university courses if in dev (ie don't use HTTP_HOST)
-MITX_FEATURES['ENABLE_MANUAL_GIT_RELOAD'] = True
-MITX_FEATURES['ENABLE_PSYCHOMETRICS'] = False # real-time psychometrics (eg item response theory analysis in instructor dashboard)
-
-
-WIKI_ENABLED = True
-
-LOGGING = get_logger_config(ENV_ROOT / "log",
- logging_env="dev",
- local_loglevel="DEBUG",
- dev_env=True,
- debug=True)
-
-DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': ENV_ROOT / "db" / "mitx.db",
- }
-}
-
-CACHES = {
- # This is the cache used for most things.
- # In staging/prod envs, the sessions also live here.
- 'default': {
- 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
- 'LOCATION': 'mitx_loc_mem_cache',
- 'KEY_FUNCTION': 'util.memcache.safe_key',
- },
-
- # The general cache is what you get if you use our util.cache. It's used for
- # things like caching the course.xml file for different A/B test groups.
- # We set it to be a DummyCache to force reloading of course.xml in dev.
- # In staging environments, we would grab VERSION from data uploaded by the
- # push process.
- #'general': {
- # 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
- # 'KEY_PREFIX': 'general',
- # 'VERSION': 4,
- # 'LOCATION': 'mitx_loc_mem_cache_general',
- # 'KEY_FUNCTION': 'util.memcache.safe_key',
- #}
-
- 'general': {
- 'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
- 'KEY_PREFIX': 'general',
- 'VERSION': 4,
- 'KEY_FUNCTION': 'util.memcache.safe_key',
- }
-}
-
-
-XQUEUE_INTERFACE = {
- "url": "http://127.0.0.1:3032",
- "django_auth": {
- "username": "lms",
- "password": "abcd"
- },
- "basic_auth": ('anant', 'agarwal'),
-}
-
-# Make the keyedcache startup warnings go away
-CACHE_TIMEOUT = 0
-
-# Dummy secret key for dev
-SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
-
-
-COURSE_LISTINGS = {
- 'default': ['BerkeleyX/CS169.1x/2012_Fall',
- 'BerkeleyX/CS188.1x/2012_Fall',
- 'HarvardX/CS50x/2012',
- 'HarvardX/PH207x/2012_Fall',
- 'MITx/3.091x/2012_Fall',
- 'MITx/6.002x/2012_Fall',
- 'MITx/6.00x/2012_Fall'],
- 'berkeley': ['BerkeleyX/CS169/fa12',
- 'BerkeleyX/CS188/fa12'],
- 'harvard': ['HarvardX/CS50x/2012H'],
- 'mit': ['MITx/3.091/MIT_2012_Fall'],
- 'sjsu': ['MITx/6.002x-EE98/2012_Fall_SJSU'],
-}
-
-
-SUBDOMAIN_BRANDING = {
- 'sjsu': 'MITx',
- 'mit': 'MITx',
- 'berkeley': 'BerkeleyX',
- 'harvard': 'HarvardX',
-}
-
-# List of `university` landing pages to display, even though they may not
-# have an actual course with that org set
-VIRTUAL_UNIVERSITIES = []
-
-COMMENTS_SERVICE_KEY = "PUT_YOUR_API_KEY_HERE"
-
-################################# mitx revision string #####################
-
-MITX_VERSION_STRING = os.popen('cd %s; git describe' % REPO_ROOT).read().strip()
-
-################################# Staff grading config #####################
-
-STAFF_GRADING_INTERFACE = {
- 'url': 'http://127.0.0.1:3033/staff_grading',
- 'username': 'lms',
- 'password': 'abcd',
- }
-
-################################# Peer grading config #####################
-
-PEER_GRADING_INTERFACE = {
- 'url': 'http://127.0.0.1:3033/peer_grading',
- 'username': 'lms',
- 'password': 'abcd',
- }
-################################ LMS Migration #################################
-MITX_FEATURES['ENABLE_LMS_MIGRATION'] = True
-MITX_FEATURES['ACCESS_REQUIRE_STAFF_FOR_COURSE'] = False # require that user be in the staff_* group to be able to enroll
-MITX_FEATURES['USE_XQA_SERVER'] = 'http://xqa:server@content-qa.mitx.mit.edu/xqa'
-
-INSTALLED_APPS += ('lms_migration',)
-
-LMS_MIGRATION_ALLOWED_IPS = ['127.0.0.1']
-
-################################ OpenID Auth #################################
-MITX_FEATURES['AUTH_USE_OPENID'] = True
-MITX_FEATURES['AUTH_USE_OPENID_PROVIDER'] = True
-MITX_FEATURES['BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'] = True
-
-INSTALLED_APPS += ('external_auth',)
-INSTALLED_APPS += ('django_openid_auth',)
-
-OPENID_CREATE_USERS = False
-OPENID_UPDATE_DETAILS_FROM_SREG = True
-OPENID_SSO_SERVER_URL = 'https://www.google.com/accounts/o8/id' # TODO: accept more endpoints
-OPENID_USE_AS_ADMIN_LOGIN = False
-
-OPENID_PROVIDER_TRUSTED_ROOTS = ['*']
-
-################################ MIT Certificates SSL Auth #################################
-
-MITX_FEATURES['AUTH_USE_MIT_CERTIFICATES'] = True
-
-################################ DEBUG TOOLBAR #################################
-INSTALLED_APPS += ('debug_toolbar',)
-MIDDLEWARE_CLASSES += ('django_comment_client.utils.QueryCountDebugMiddleware',
- 'debug_toolbar.middleware.DebugToolbarMiddleware',)
-INTERNAL_IPS = ('127.0.0.1',)
-
-DEBUG_TOOLBAR_PANELS = (
- 'debug_toolbar.panels.version.VersionDebugPanel',
- 'debug_toolbar.panels.timer.TimerDebugPanel',
- 'debug_toolbar.panels.settings_vars.SettingsVarsDebugPanel',
- 'debug_toolbar.panels.headers.HeaderDebugPanel',
- 'debug_toolbar.panels.request_vars.RequestVarsDebugPanel',
- 'debug_toolbar.panels.sql.SQLDebugPanel',
- 'debug_toolbar.panels.signals.SignalDebugPanel',
- 'debug_toolbar.panels.logger.LoggingPanel',
-
-# Enabling the profiler has a weird bug as of django-debug-toolbar==0.9.4 and
-# Django=1.3.1/1.4 where requests to views get duplicated (your method gets
-# hit twice). So you can uncomment when you need to diagnose performance
-# problems, but you shouldn't leave it on.
-# 'debug_toolbar.panels.profiling.ProfilingDebugPanel',
-)
-
-DEBUG_TOOLBAR_CONFIG = {
- 'INTERCEPT_REDIRECTS': False
-}
-############################ FILE UPLOADS (for discussion forums) #############################
-DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
-MEDIA_ROOT = ENV_ROOT / "uploads"
-MEDIA_URL = "/static/uploads/"
-STATICFILES_DIRS.append(("uploads", MEDIA_ROOT))
-FILE_UPLOAD_TEMP_DIR = ENV_ROOT / "uploads"
-FILE_UPLOAD_HANDLERS = (
- 'django.core.files.uploadhandler.MemoryFileUploadHandler',
- 'django.core.files.uploadhandler.TemporaryFileUploadHandler',
-)
-
-########################### PIPELINE #################################
-
-PIPELINE_SASS_ARGUMENTS = '-r {proj_dir}/static/sass/bourbon/lib/bourbon.rb'.format(proj_dir=PROJECT_ROOT)
-
-########################## PEARSON TESTING ###########################
-MITX_FEATURES['ENABLE_PEARSON_HACK_TEST'] = True
-PEARSON_TEST_USER = "pearsontest"
-PEARSON_TEST_PASSWORD = "12345"
-
-#AWS upload stuff for local file testing
-AWS_ACCESS_KEY_ID = "***REMOVED***"
-AWS_SECRET_ACCESS_KEY = "***REMOVED***"
-AWS_STORAGE_BUCKET_NAME = 'edxuploads'
-
From 81b86bb8fe795a201f126239d229b6f5554a2732 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Mon, 4 Feb 2013 14:16:26 -0500
Subject: [PATCH 062/132] Retrack dev
---
lms/envs/dev.py | 200 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 200 insertions(+)
create mode 100644 lms/envs/dev.py
diff --git a/lms/envs/dev.py b/lms/envs/dev.py
new file mode 100644
index 0000000000..99ee9662ee
--- /dev/null
+++ b/lms/envs/dev.py
@@ -0,0 +1,200 @@
+"""
+This config file runs the simplest dev environment using sqlite, and db-based
+sessions. Assumes structure:
+
+/envroot/
+ /db # This is where it'll write the database file
+ /mitx # The location of this repo
+ /log # Where we're going to write log files
+"""
+from .common import *
+from logsettings import get_logger_config
+
+DEBUG = True
+TEMPLATE_DEBUG = True
+
+
+MITX_FEATURES['DISABLE_START_DATES'] = True
+MITX_FEATURES['ENABLE_SQL_TRACKING_LOGS'] = True
+MITX_FEATURES['SUBDOMAIN_COURSE_LISTINGS'] = False # Enable to test subdomains--otherwise, want all courses to show up
+MITX_FEATURES['SUBDOMAIN_BRANDING'] = True
+MITX_FEATURES['FORCE_UNIVERSITY_DOMAIN'] = None # show all university courses if in dev (ie don't use HTTP_HOST)
+MITX_FEATURES['ENABLE_MANUAL_GIT_RELOAD'] = True
+MITX_FEATURES['ENABLE_PSYCHOMETRICS'] = False # real-time psychometrics (eg item response theory analysis in instructor dashboard)
+
+
+WIKI_ENABLED = True
+
+LOGGING = get_logger_config(ENV_ROOT / "log",
+ logging_env="dev",
+ local_loglevel="DEBUG",
+ dev_env=True,
+ debug=True)
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': ENV_ROOT / "db" / "mitx.db",
+ }
+}
+
+CACHES = {
+ # This is the cache used for most things.
+ # In staging/prod envs, the sessions also live here.
+ 'default': {
+ 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
+ 'LOCATION': 'mitx_loc_mem_cache',
+ 'KEY_FUNCTION': 'util.memcache.safe_key',
+ },
+
+ # The general cache is what you get if you use our util.cache. It's used for
+ # things like caching the course.xml file for different A/B test groups.
+ # We set it to be a DummyCache to force reloading of course.xml in dev.
+ # In staging environments, we would grab VERSION from data uploaded by the
+ # push process.
+ 'general': {
+ 'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
+ 'KEY_PREFIX': 'general',
+ 'VERSION': 4,
+ 'KEY_FUNCTION': 'util.memcache.safe_key',
+ }
+}
+
+
+XQUEUE_INTERFACE = {
+ "url": "https://sandbox-xqueue.edx.org",
+ "django_auth": {
+ "username": "lms",
+ "password": "***REMOVED***"
+ },
+ "basic_auth": ('anant', 'agarwal'),
+}
+
+# Make the keyedcache startup warnings go away
+CACHE_TIMEOUT = 0
+
+# Dummy secret key for dev
+SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
+
+
+COURSE_LISTINGS = {
+ 'default': ['BerkeleyX/CS169.1x/2012_Fall',
+ 'BerkeleyX/CS188.1x/2012_Fall',
+ 'HarvardX/CS50x/2012',
+ 'HarvardX/PH207x/2012_Fall',
+ 'MITx/3.091x/2012_Fall',
+ 'MITx/6.002x/2012_Fall',
+ 'MITx/6.00x/2012_Fall'],
+ 'berkeley': ['BerkeleyX/CS169/fa12',
+ 'BerkeleyX/CS188/fa12'],
+ 'harvard': ['HarvardX/CS50x/2012H'],
+ 'mit': ['MITx/3.091/MIT_2012_Fall'],
+ 'sjsu': ['MITx/6.002x-EE98/2012_Fall_SJSU'],
+}
+
+
+SUBDOMAIN_BRANDING = {
+ 'sjsu': 'MITx',
+ 'mit': 'MITx',
+ 'berkeley': 'BerkeleyX',
+ 'harvard': 'HarvardX',
+}
+
+# List of `university` landing pages to display, even though they may not
+# have an actual course with that org set
+VIRTUAL_UNIVERSITIES = []
+
+COMMENTS_SERVICE_KEY = "PUT_YOUR_API_KEY_HERE"
+
+################################# mitx revision string #####################
+
+MITX_VERSION_STRING = os.popen('cd %s; git describe' % REPO_ROOT).read().strip()
+
+################################# Staff grading config #####################
+
+STAFF_GRADING_INTERFACE = {
+ 'url': 'http://127.0.0.1:3033/staff_grading',
+ 'username': 'lms',
+ 'password': 'abcd',
+ }
+
+################################# Peer grading config #####################
+
+PEER_GRADING_INTERFACE = {
+ 'url': 'http://127.0.0.1:3033/peer_grading',
+ 'username': 'lms',
+ 'password': 'abcd',
+ }
+################################ LMS Migration #################################
+MITX_FEATURES['ENABLE_LMS_MIGRATION'] = True
+MITX_FEATURES['ACCESS_REQUIRE_STAFF_FOR_COURSE'] = False # require that user be in the staff_* group to be able to enroll
+MITX_FEATURES['USE_XQA_SERVER'] = 'http://xqa:server@content-qa.mitx.mit.edu/xqa'
+
+INSTALLED_APPS += ('lms_migration',)
+
+LMS_MIGRATION_ALLOWED_IPS = ['127.0.0.1']
+
+################################ OpenID Auth #################################
+MITX_FEATURES['AUTH_USE_OPENID'] = True
+MITX_FEATURES['AUTH_USE_OPENID_PROVIDER'] = True
+MITX_FEATURES['BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'] = True
+
+INSTALLED_APPS += ('external_auth',)
+INSTALLED_APPS += ('django_openid_auth',)
+
+OPENID_CREATE_USERS = False
+OPENID_UPDATE_DETAILS_FROM_SREG = True
+OPENID_SSO_SERVER_URL = 'https://www.google.com/accounts/o8/id' # TODO: accept more endpoints
+OPENID_USE_AS_ADMIN_LOGIN = False
+
+OPENID_PROVIDER_TRUSTED_ROOTS = ['*']
+
+################################ MIT Certificates SSL Auth #################################
+
+MITX_FEATURES['AUTH_USE_MIT_CERTIFICATES'] = True
+
+################################ DEBUG TOOLBAR #################################
+INSTALLED_APPS += ('debug_toolbar',)
+MIDDLEWARE_CLASSES += ('django_comment_client.utils.QueryCountDebugMiddleware',
+ 'debug_toolbar.middleware.DebugToolbarMiddleware',)
+INTERNAL_IPS = ('127.0.0.1',)
+
+DEBUG_TOOLBAR_PANELS = (
+ 'debug_toolbar.panels.version.VersionDebugPanel',
+ 'debug_toolbar.panels.timer.TimerDebugPanel',
+ 'debug_toolbar.panels.settings_vars.SettingsVarsDebugPanel',
+ 'debug_toolbar.panels.headers.HeaderDebugPanel',
+ 'debug_toolbar.panels.request_vars.RequestVarsDebugPanel',
+ 'debug_toolbar.panels.sql.SQLDebugPanel',
+ 'debug_toolbar.panels.signals.SignalDebugPanel',
+ 'debug_toolbar.panels.logger.LoggingPanel',
+
+# Enabling the profiler has a weird bug as of django-debug-toolbar==0.9.4 and
+# Django=1.3.1/1.4 where requests to views get duplicated (your method gets
+# hit twice). So you can uncomment when you need to diagnose performance
+# problems, but you shouldn't leave it on.
+# 'debug_toolbar.panels.profiling.ProfilingDebugPanel',
+)
+
+DEBUG_TOOLBAR_CONFIG = {
+ 'INTERCEPT_REDIRECTS': False
+}
+############################ FILE UPLOADS (for discussion forums) #############################
+DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
+MEDIA_ROOT = ENV_ROOT / "uploads"
+MEDIA_URL = "/static/uploads/"
+STATICFILES_DIRS.append(("uploads", MEDIA_ROOT))
+FILE_UPLOAD_TEMP_DIR = ENV_ROOT / "uploads"
+FILE_UPLOAD_HANDLERS = (
+ 'django.core.files.uploadhandler.MemoryFileUploadHandler',
+ 'django.core.files.uploadhandler.TemporaryFileUploadHandler',
+)
+
+########################### PIPELINE #################################
+
+PIPELINE_SASS_ARGUMENTS = '-r {proj_dir}/static/sass/bourbon/lib/bourbon.rb'.format(proj_dir=PROJECT_ROOT)
+
+########################## PEARSON TESTING ###########################
+MITX_FEATURES['ENABLE_PEARSON_HACK_TEST'] = True
+PEARSON_TEST_USER = "pearsontest"
+PEARSON_TEST_PASSWORD = "12345"
From 6cf2742e32f9ace474b0a1e4ae4d898b0c868c3c Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Mon, 4 Feb 2013 14:22:57 -0500
Subject: [PATCH 063/132] Trim unneeded files
---
.../peer_grading_service.py | 380 --------------
lms/djangoapps/open_ended_grading/views.py | 6 -
.../src/peer_grading/peer_grading.coffee | 27 -
.../peer_grading/peer_grading_problem.coffee | 478 ------------------
.../peer_grading_notifications.html | 17 -
5 files changed, 908 deletions(-)
delete mode 100644 lms/djangoapps/open_ended_grading/peer_grading_service.py
delete mode 100644 lms/static/coffee/src/peer_grading/peer_grading.coffee
delete mode 100644 lms/static/coffee/src/peer_grading/peer_grading_problem.coffee
delete mode 100644 lms/templates/peer_grading/peer_grading_notifications.html
diff --git a/lms/djangoapps/open_ended_grading/peer_grading_service.py b/lms/djangoapps/open_ended_grading/peer_grading_service.py
deleted file mode 100644
index 994ba0b2be..0000000000
--- a/lms/djangoapps/open_ended_grading/peer_grading_service.py
+++ /dev/null
@@ -1,380 +0,0 @@
-"""
-This module provides an interface on the grading-service backend
-for peer grading
-
-Use peer_grading_service() to get the version specified
-in settings.PEER_GRADING_INTERFACE
-
-"""
-import json
-import logging
-import requests
-from requests.exceptions import RequestException, ConnectionError, HTTPError
-import sys
-
-from django.conf import settings
-from django.http import HttpResponse, Http404
-from grading_service import GradingService
-from grading_service import GradingServiceError
-
-from courseware.access import has_access
-from util.json_request import expect_json
-from xmodule.course_module import CourseDescriptor
-from xmodule.combined_open_ended_rubric import CombinedOpenEndedRubric
-from student.models import unique_id_for_user
-from lxml import etree
-
-log = logging.getLogger(__name__)
-
-"""
-This is a mock peer grading service that can be used for unit tests
-without making actual service calls to the grading controller
-"""
-class MockPeerGradingService(object):
- def get_next_submission(self, problem_location, grader_id):
- return json.dumps({'success': True,
- 'submission_id':1,
- 'submission_key': "",
- 'student_response': 'fake student response',
- 'prompt': 'fake submission prompt',
- 'rubric': 'fake rubric',
- 'max_score': 4})
-
- def save_grade(self, location, grader_id, submission_id,
- score, feedback, submission_key):
- return json.dumps({'success': True})
-
- def is_student_calibrated(self, problem_location, grader_id):
- return json.dumps({'success': True, 'calibrated': True})
-
- def show_calibration_essay(self, problem_location, grader_id):
- return json.dumps({'success': True,
- 'submission_id':1,
- 'submission_key': '',
- 'student_response': 'fake student response',
- 'prompt': 'fake submission prompt',
- 'rubric': 'fake rubric',
- 'max_score': 4})
-
- def save_calibration_essay(self, problem_location, grader_id,
- calibration_essay_id, submission_key, score, feedback):
- return {'success': True, 'actual_score': 2}
-
- def get_problem_list(self, course_id, grader_id):
- return json.dumps({'success': True,
- 'problem_list': [
- json.dumps({'location': 'i4x://MITx/3.091x/problem/open_ended_demo1',
- 'problem_name': "Problem 1", 'num_graded': 3, 'num_pending': 5}),
- json.dumps({'location': 'i4x://MITx/3.091x/problem/open_ended_demo2',
- 'problem_name': "Problem 2", 'num_graded': 1, 'num_pending': 5})
- ]})
-
-class PeerGradingService(GradingService):
- """
- Interface with the grading controller for peer grading
- """
- def __init__(self, config):
- super(PeerGradingService, self).__init__(config)
- self.get_next_submission_url = self.url + '/get_next_submission/'
- self.save_grade_url = self.url + '/save_grade/'
- self.is_student_calibrated_url = self.url + '/is_student_calibrated/'
- self.show_calibration_essay_url = self.url + '/show_calibration_essay/'
- self.save_calibration_essay_url = self.url + '/save_calibration_essay/'
- self.get_problem_list_url = self.url + '/get_problem_list/'
- self.get_notifications_url = self.url + '/get_notifications/'
-
- def get_next_submission(self, problem_location, grader_id):
- response = self.get(self.get_next_submission_url,
- {'location': problem_location, 'grader_id': grader_id})
- return json.dumps(self._render_rubric(response))
-
- def save_grade(self, location, grader_id, submission_id, score, feedback, submission_key, rubric_scores, submission_flagged):
- data = {'grader_id' : grader_id,
- 'submission_id' : submission_id,
- 'score' : score,
- 'feedback' : feedback,
- 'submission_key': submission_key,
- 'location': location,
- 'rubric_scores': rubric_scores,
- 'rubric_scores_complete': True,
- 'submission_flagged' : submission_flagged}
- return self.post(self.save_grade_url, data)
-
- def is_student_calibrated(self, problem_location, grader_id):
- params = {'problem_id' : problem_location, 'student_id': grader_id}
- return self.get(self.is_student_calibrated_url, params)
-
- def show_calibration_essay(self, problem_location, grader_id):
- params = {'problem_id' : problem_location, 'student_id': grader_id}
- response = self.get(self.show_calibration_essay_url, params)
- return json.dumps(self._render_rubric(response))
-
- def save_calibration_essay(self, problem_location, grader_id, calibration_essay_id, submission_key,
- score, feedback, rubric_scores):
- data = {'location': problem_location,
- 'student_id': grader_id,
- 'calibration_essay_id': calibration_essay_id,
- 'submission_key': submission_key,
- 'score': score,
- 'feedback': feedback,
- 'rubric_scores[]': rubric_scores,
- 'rubric_scores_complete': True}
- return self.post(self.save_calibration_essay_url, data)
-
- def get_problem_list(self, course_id, grader_id):
- params = {'course_id': course_id, 'student_id': grader_id}
- response = self.get(self.get_problem_list_url, params)
- return response
-
- def get_notifications(self, course_id, grader_id):
- params = {'course_id': course_id, 'student_id': grader_id}
- response = self.get(self.get_notifications_url, params)
- return response
-
-
-_service = None
-def peer_grading_service():
- """
- Return a peer grading service instance--if settings.MOCK_PEER_GRADING is True,
- returns a mock one, otherwise a real one.
-
- Caches the result, so changing the setting after the first call to this
- function will have no effect.
- """
- global _service
- if _service is not None:
- return _service
-
- if settings.MOCK_PEER_GRADING:
- _service = MockPeerGradingService()
- else:
- _service = PeerGradingService(settings.PEER_GRADING_INTERFACE)
-
- return _service
-
-def _err_response(msg):
- """
- Return a HttpResponse with a json dump with success=False, and the given error message.
- """
- return HttpResponse(json.dumps({'success': False, 'error': msg}),
- mimetype="application/json")
-
-def _check_required(request, required):
- actual = set(request.POST.keys())
- missing = required - actual
- if len(missing) > 0:
- return False, "Missing required keys: {0}".format(', '.join(missing))
- else:
- return True, ""
-
-def _check_post(request):
- if request.method != 'POST':
- raise Http404
-
-
-def get_next_submission(request, course_id):
- """
- Makes a call to the grading controller for the next essay that should be graded
- Returns a json dict with the following keys:
-
- 'success': bool
-
- 'submission_id': a unique identifier for the submission, to be passed back
- with the grade.
-
- 'submission': the submission, rendered as read-only html for grading
-
- 'rubric': the rubric, also rendered as html.
-
- 'submission_key': a key associated with the submission for validation reasons
-
- 'error': if success is False, will have an error message with more info.
- """
- _check_post(request)
- required = set(['location'])
- success, message = _check_required(request, required)
- if not success:
- return _err_response(message)
- grader_id = unique_id_for_user(request.user)
- p = request.POST
- location = p['location']
-
- try:
- response = peer_grading_service().get_next_submission(location, grader_id)
- return HttpResponse(response,
- mimetype="application/json")
- except GradingServiceError:
- log.exception("Error getting next submission. server url: {0} location: {1}, grader_id: {2}"
- .format(peer_grading_service().url, location, grader_id))
- return json.dumps({'success': False,
- 'error': 'Could not connect to grading service'})
-
-def save_grade(request, course_id):
- """
- Saves the grade of a given submission.
- Input:
- The request should have the following keys:
- location - problem location
- submission_id - id associated with this submission
- submission_key - submission key given for validation purposes
- score - the grade that was given to the submission
- feedback - the feedback from the student
- Returns
- A json object with the following keys:
- success: bool indicating whether the save was a success
- error: if there was an error in the submission, this is the error message
- """
- _check_post(request)
- required = set(['location', 'submission_id', 'submission_key', 'score', 'feedback', 'rubric_scores[]', 'submission_flagged'])
- success, message = _check_required(request, required)
- if not success:
- return _err_response(message)
- grader_id = unique_id_for_user(request.user)
- p = request.POST
- location = p['location']
- submission_id = p['submission_id']
- score = p['score']
- feedback = p['feedback']
- submission_key = p['submission_key']
- rubric_scores = p.getlist('rubric_scores[]')
- submission_flagged = p['submission_flagged']
- try:
- response = peer_grading_service().save_grade(location, grader_id, submission_id,
- score, feedback, submission_key, rubric_scores, submission_flagged)
- return HttpResponse(response, mimetype="application/json")
- except GradingServiceError:
- log.exception("""Error saving grade. server url: {0}, location: {1}, submission_id:{2},
- submission_key: {3}, score: {4}"""
- .format(peer_grading_service().url,
- location, submission_id, submission_key, score)
- )
- return json.dumps({'success': False,
- 'error': 'Could not connect to grading service'})
-
-
-
-def is_student_calibrated(request, course_id):
- """
- Calls the grading controller to see if the given student is calibrated
- on the given problem
-
- Input:
- In the request, we need the following arguments:
- location - problem location
-
- Returns:
- Json object with the following keys
- success - bool indicating whether or not the call was successful
- calibrated - true if the grader has fully calibrated and can now move on to grading
- - false if the grader is still working on calibration problems
- total_calibrated_on_so_far - the number of calibration essays for this problem
- that this grader has graded
- """
- _check_post(request)
- required = set(['location'])
- success, message = _check_required(request, required)
- if not success:
- return _err_response(message)
- grader_id = unique_id_for_user(request.user)
- p = request.POST
- location = p['location']
-
- try:
- response = peer_grading_service().is_student_calibrated(location, grader_id)
- return HttpResponse(response, mimetype="application/json")
- except GradingServiceError:
- log.exception("Error from grading service. server url: {0}, grader_id: {0}, location: {1}"
- .format(peer_grading_service().url, grader_id, location))
- return json.dumps({'success': False,
- 'error': 'Could not connect to grading service'})
-
-
-
-def show_calibration_essay(request, course_id):
- """
- Fetch the next calibration essay from the grading controller and return it
- Inputs:
- In the request
- location - problem location
-
- Returns:
- A json dict with the following keys
- 'success': bool
-
- 'submission_id': a unique identifier for the submission, to be passed back
- with the grade.
-
- 'submission': the submission, rendered as read-only html for grading
-
- 'rubric': the rubric, also rendered as html.
-
- 'submission_key': a key associated with the submission for validation reasons
-
- 'error': if success is False, will have an error message with more info.
-
- """
- _check_post(request)
-
- required = set(['location'])
- success, message = _check_required(request, required)
- if not success:
- return _err_response(message)
-
- grader_id = unique_id_for_user(request.user)
- p = request.POST
- location = p['location']
- try:
- response = peer_grading_service().show_calibration_essay(location, grader_id)
- return HttpResponse(response, mimetype="application/json")
- except GradingServiceError:
- log.exception("Error from grading service. server url: {0}, location: {0}"
- .format(peer_grading_service().url, location))
- return json.dumps({'success': False,
- 'error': 'Could not connect to grading service'})
- # if we can't parse the rubric into HTML,
- except etree.XMLSyntaxError:
- log.exception("Cannot parse rubric string. Raw string: {0}"
- .format(rubric))
- return json.dumps({'success': False,
- 'error': 'Error displaying submission'})
-
-
-def save_calibration_essay(request, course_id):
- """
- Saves the grader's grade of a given calibration.
- Input:
- The request should have the following keys:
- location - problem location
- submission_id - id associated with this submission
- submission_key - submission key given for validation purposes
- score - the grade that was given to the submission
- feedback - the feedback from the student
- Returns
- A json object with the following keys:
- success: bool indicating whether the save was a success
- error: if there was an error in the submission, this is the error message
- actual_score: the score that the instructor gave to this calibration essay
-
- """
- _check_post(request)
-
- required = set(['location', 'submission_id', 'submission_key', 'score', 'feedback', 'rubric_scores[]'])
- success, message = _check_required(request, required)
- if not success:
- return _err_response(message)
- grader_id = unique_id_for_user(request.user)
- p = request.POST
- location = p['location']
- calibration_essay_id = p['submission_id']
- submission_key = p['submission_key']
- score = p['score']
- feedback = p['feedback']
- rubric_scores = p.getlist('rubric_scores[]')
-
- try:
- response = peer_grading_service().save_calibration_essay(location, grader_id, calibration_essay_id,
- submission_key, score, feedback, rubric_scores)
- return HttpResponse(response, mimetype="application/json")
- except GradingServiceError:
- log.exception("Error saving calibration grade, location: {0}, submission_id: {1}, submission_key: {2}, grader_id: {3}".format(location, submission_id, submission_key, grader_id))
- return _err_response('Could not connect to grading service')
\ No newline at end of file
diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py
index c20ff85ee0..5163702343 100644
--- a/lms/djangoapps/open_ended_grading/views.py
+++ b/lms/djangoapps/open_ended_grading/views.py
@@ -12,8 +12,6 @@ from django.core.urlresolvers import reverse
from student.models import unique_id_for_user
from courseware.courses import get_course_with_access
-from peer_grading_service import PeerGradingService
-from peer_grading_service import MockPeerGradingService
from controller_query_service import ControllerQueryService
from grading_service import GradingServiceError
import json
@@ -39,10 +37,6 @@ from django.http import HttpResponse, Http404, HttpResponseRedirect
log = logging.getLogger(__name__)
template_imports = {'urllib': urllib}
-if settings.MOCK_PEER_GRADING:
- peer_gs = MockPeerGradingService()
-else:
- peer_gs = PeerGradingService(settings.PEER_GRADING_INTERFACE)
controller_url = open_ended_util.get_controller_url()
controller_qs = ControllerQueryService(controller_url)
diff --git a/lms/static/coffee/src/peer_grading/peer_grading.coffee b/lms/static/coffee/src/peer_grading/peer_grading.coffee
deleted file mode 100644
index ed79ba9c71..0000000000
--- a/lms/static/coffee/src/peer_grading/peer_grading.coffee
+++ /dev/null
@@ -1,27 +0,0 @@
-# This is a simple class that just hides the error container
-# and message container when they are empty
-# Can (and should be) expanded upon when our problem list
-# becomes more sophisticated
-class PeerGrading
- constructor: () ->
- @error_container = $('.error-container')
- @error_container.toggle(not @error_container.is(':empty'))
-
- @message_container = $('.message-container')
- @message_container.toggle(not @message_container.is(':empty'))
-
- @problem_list = $('.problem-list')
- @construct_progress_bar()
-
- construct_progress_bar: () =>
- problems = @problem_list.find('tr').next()
- problems.each( (index, element) =>
- problem = $(element)
- progress_bar = problem.find('.progress-bar')
- bar_value = parseInt(problem.data('graded'))
- bar_max = parseInt(problem.data('required')) + bar_value
- progress_bar.progressbar({value: bar_value, max: bar_max})
- )
-
-
-$(document).ready(() -> new PeerGrading())
diff --git a/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee b/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee
deleted file mode 100644
index ab16b34d12..0000000000
--- a/lms/static/coffee/src/peer_grading/peer_grading_problem.coffee
+++ /dev/null
@@ -1,478 +0,0 @@
-##################################
-#
-# This is the JS that renders the peer grading problem page.
-# Fetches the correct problem and/or calibration essay
-# and sends back the grades
-#
-# Should not be run when we don't have a location to send back
-# to the server
-#
-# PeerGradingProblemBackend -
-# makes all the ajax requests and provides a mock interface
-# for testing purposes
-#
-# PeerGradingProblem -
-# handles the rendering and user interactions with the interface
-#
-##################################
-class PeerGradingProblemBackend
- constructor: (ajax_url, mock_backend) ->
- @mock_backend = mock_backend
- @ajax_url = ajax_url
- @mock_cnt = 0
-
- post: (cmd, data, callback) ->
- if @mock_backend
- callback(@mock(cmd, data))
- else
- # if this post request fails, the error callback will catch it
- $.post(@ajax_url + cmd, data, callback)
- .error => callback({success: false, error: "Error occured while performing this operation"})
-
- mock: (cmd, data) ->
- if cmd == 'is_student_calibrated'
- # change to test each version
- response =
- success: true
- calibrated: @mock_cnt >= 2
- else if cmd == 'show_calibration_essay'
- #response =
- # success: false
- # error: "There was an error"
- @mock_cnt++
- response =
- success: true
- submission_id: 1
- submission_key: 'abcd'
- student_response: '''
- Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.
-
-The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.
- '''
- prompt: '''
-
S11E3: Metal Bands
-
Shown below are schematic band diagrams for two different metals. Both diagrams appear different, yet both of the elements are undisputably metallic in nature.
-
* Why is it that both sodium and magnesium behave as metals, even though the s-band of magnesium is filled?
-
This is a self-assessed open response question. Please use as much space as you need in the box below to answer the question.
- '''
- rubric: '''
-
Purpose
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Organization
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- '''
- max_score: 4
- else if cmd == 'get_next_submission'
- response =
- success: true
- submission_id: 1
- submission_key: 'abcd'
- student_response: '''Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nec tristique ante. Proin at mauris sapien, quis varius leo. Morbi laoreet leo nisi. Morbi aliquam lacus ante. Cras iaculis velit sed diam mattis a fermentum urna luctus. Duis consectetur nunc vitae felis facilisis eget vulputate risus viverra. Cras consectetur ullamcorper lobortis. Nam eu gravida lorem. Nulla facilisi. Nullam quis felis enim. Mauris orci lectus, dictum id cursus in, vulputate in massa.
-
-Phasellus non varius sem. Nullam commodo lacinia odio sit amet egestas. Donec ullamcorper sapien sagittis arcu volutpat placerat. Phasellus ut pretium ante. Nam dictum pulvinar nibh dapibus tristique. Sed at tellus mi, fringilla convallis justo. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus tristique rutrum nulla sed eleifend. Praesent at nunc arcu. Mauris condimentum faucibus nibh, eget commodo quam viverra sed. Morbi in tincidunt dolor. Morbi sed augue et augue interdum fermentum.
-
-Curabitur tristique purus ac arcu consequat cursus. Cras diam felis, dignissim quis placerat at, aliquet ac metus. Mauris vulputate est eu nibh imperdiet varius. Cras aliquet rhoncus elit a laoreet. Mauris consectetur erat et erat scelerisque eu faucibus dolor consequat. Nam adipiscing sagittis nisl, eu mollis massa tempor ac. Nulla scelerisque tempus blandit. Phasellus ac ipsum eros, id posuere arcu. Nullam non sapien arcu. Vivamus sit amet lorem justo, ac tempus turpis. Suspendisse pharetra gravida imperdiet. Pellentesque lacinia mi eu elit luctus pellentesque. Sed accumsan libero a magna elementum varius. Nunc eget pellentesque metus. '''
- prompt: '''
-
S11E3: Metal Bands
-
Shown below are schematic band diagrams for two different metals. Both diagrams appear different, yet both of the elements are undisputably metallic in nature.
-
* Why is it that both sodium and magnesium behave as metals, even though the s-band of magnesium is filled?
-
This is a self-assessed open response question. Please use as much space as you need in the box below to answer the question.
- '''
- rubric: '''
-
Purpose
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Organization
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- '''
- max_score: 4
- else if cmd == 'save_calibration_essay'
- response =
- success: true
- actual_score: 2
- else if cmd == 'save_grade'
- response =
- success: true
-
- return response
-
-
-class PeerGradingProblem
- constructor: (backend) ->
- @prompt_wrapper = $('.prompt-wrapper')
- @backend = backend
-
-
- # get the location of the problem
- @location = $('.peer-grading').data('location')
- # prevent this code from trying to run
- # when we don't have a location
- if(!@location)
- return
-
- # get the other elements we want to fill in
- @submission_container = $('.submission-container')
- @prompt_container = $('.prompt-container')
- @rubric_container = $('.rubric-container')
- @flag_student_container = $('.flag-student-container')
- @calibration_panel = $('.calibration-panel')
- @grading_panel = $('.grading-panel')
- @content_panel = $('.content-panel')
- @grading_message = $('.grading-message')
- @grading_message.hide()
-
- @grading_wrapper =$('.grading-wrapper')
- @calibration_feedback_panel = $('.calibration-feedback')
- @interstitial_page = $('.interstitial-page')
- @interstitial_page.hide()
-
- @error_container = $('.error-container')
-
- @submission_key_input = $("input[name='submission-key']")
- @essay_id_input = $("input[name='essay-id']")
- @feedback_area = $('.feedback-area')
-
- @score_selection_container = $('.score-selection-container')
- @rubric_selection_container = $('.rubric-selection-container')
- @grade = null
- @calibration = null
-
- @submit_button = $('.submit-button')
- @action_button = $('.action-button')
- @calibration_feedback_button = $('.calibration-feedback-button')
- @interstitial_page_button = $('.interstitial-page-button')
- @flag_student_checkbox = $('.flag-checkbox')
-
- Collapsible.setCollapsibles(@content_panel)
-
- # Set up the click event handlers
- @action_button.click -> history.back()
- @calibration_feedback_button.click =>
- @calibration_feedback_panel.hide()
- @grading_wrapper.show()
- @is_calibrated_check()
-
- @interstitial_page_button.click =>
- @interstitial_page.hide()
- @is_calibrated_check()
-
- @is_calibrated_check()
-
-
- ##########
- #
- # Ajax calls to the backend
- #
- ##########
- is_calibrated_check: () =>
- @backend.post('is_student_calibrated', {location: @location}, @calibration_check_callback)
-
- fetch_calibration_essay: () =>
- @backend.post('show_calibration_essay', {location: @location}, @render_calibration)
-
- fetch_submission_essay: () =>
- @backend.post('get_next_submission', {location: @location}, @render_submission)
-
- # finds the scores for each rubric category
- get_score_list: () =>
- # find the number of categories:
- num_categories = $('table.rubric tr').length
-
- score_lst = []
- # get the score for each one
- for i in [0..(num_categories-1)]
- score = $("input[name='score-selection-#{i}']:checked").val()
- score_lst.push(score)
-
- return score_lst
-
- construct_data: () ->
- data =
- rubric_scores: @get_score_list()
- score: @grade
- location: @location
- submission_id: @essay_id_input.val()
- submission_key: @submission_key_input.val()
- feedback: @feedback_area.val()
- submission_flagged: @flag_student_checkbox.is(':checked')
- return data
-
-
- submit_calibration_essay: ()=>
- data = @construct_data()
- @backend.post('save_calibration_essay', data, @calibration_callback)
-
- submit_grade: () =>
- data = @construct_data()
- @backend.post('save_grade', data, @submission_callback)
-
-
- ##########
- #
- # Callbacks for various events
- #
- ##########
-
- # called after we perform an is_student_calibrated check
- calibration_check_callback: (response) =>
- if response.success
- # if we haven't been calibrating before
- if response.calibrated and (@calibration == null or @calibration == false)
- @calibration = false
- @fetch_submission_essay()
- # If we were calibrating before and no longer need to,
- # show the interstitial page
- else if response.calibrated and @calibration == true
- @calibration = false
- @render_interstitial_page()
- else
- @calibration = true
- @fetch_calibration_essay()
- else if response.error
- @render_error(response.error)
- else
- @render_error("Error contacting the grading service")
-
-
- # called after we submit a calibration score
- calibration_callback: (response) =>
- if response.success
- @render_calibration_feedback(response)
- else if response.error
- @render_error(response.error)
- else
- @render_error("Error saving calibration score")
-
- # called after we submit a submission score
- submission_callback: (response) =>
- if response.success
- @is_calibrated_check()
- @grading_message.fadeIn()
- @grading_message.html("
Grade sent successfully.
")
- else
- if response.error
- @render_error(response.error)
- else
- @render_error("Error occurred while submitting grade")
-
- # called after a grade is selected on the interface
- graded_callback: (event) =>
- @grade = $("input[name='grade-selection']:checked").val()
- if @grade == undefined
- return
- # check to see whether or not any categories have not been scored
- num_categories = $('table.rubric tr').length
- for i in [0..(num_categories-1)]
- score = $("input[name='score-selection-#{i}']:checked").val()
- if score == undefined
- return
- # show button if we have scores for all categories
- @show_submit_button()
-
-
-
- ##########
- #
- # Rendering methods and helpers
- #
- ##########
- # renders a calibration essay
- render_calibration: (response) =>
- if response.success
-
- # load in all the data
- @submission_container.html("
Training Essay
")
- @render_submission_data(response)
- # TODO: indicate that we're in calibration mode
- @calibration_panel.addClass('current-state')
- @grading_panel.removeClass('current-state')
-
- # Display the right text
- # both versions of the text are written into the template itself
- # we only need to show/hide the correct ones at the correct time
- @calibration_panel.find('.calibration-text').show()
- @grading_panel.find('.calibration-text').show()
- @calibration_panel.find('.grading-text').hide()
- @grading_panel.find('.grading-text').hide()
- @flag_student_container.hide()
-
- @submit_button.unbind('click')
- @submit_button.click @submit_calibration_essay
-
- else if response.error
- @render_error(response.error)
- else
- @render_error("An error occurred while retrieving the next calibration essay")
-
- # Renders a student submission to be graded
- render_submission: (response) =>
- if response.success
- @submit_button.hide()
- @submission_container.html("
Submitted Essay
")
- @render_submission_data(response)
-
- @calibration_panel.removeClass('current-state')
- @grading_panel.addClass('current-state')
-
- # Display the correct text
- # both versions of the text are written into the template itself
- # we only need to show/hide the correct ones at the correct time
- @calibration_panel.find('.calibration-text').hide()
- @grading_panel.find('.calibration-text').hide()
- @calibration_panel.find('.grading-text').show()
- @grading_panel.find('.grading-text').show()
- @flag_student_container.show()
-
- @submit_button.unbind('click')
- @submit_button.click @submit_grade
- else if response.error
- @render_error(response.error)
- else
- @render_error("An error occured when retrieving the next submission.")
-
-
- make_paragraphs: (text) ->
- paragraph_split = text.split(/\n\s*\n/)
- new_text = ''
- for paragraph in paragraph_split
- new_text += "