Merge branch 'feature/btalbot/studio-checklists' of github.com:MITx/mitx into feature/btalbot/studio-checklists
This commit is contained in:
10
.pylintrc
10
.pylintrc
@@ -33,7 +33,11 @@ load-plugins=
|
||||
# can either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time (only on the command line, not in the configuration file where
|
||||
# it should appear only once).
|
||||
disable=E1102,W0142
|
||||
disable=
|
||||
# W0141: Used builtin function 'map'
|
||||
# W0142: Used * or ** magic
|
||||
# R0903: Too few public methods (1/2)
|
||||
W0141,W0142,R0903
|
||||
|
||||
|
||||
[REPORTS]
|
||||
@@ -97,7 +101,7 @@ bad-functions=map,filter,apply,input
|
||||
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
|
||||
|
||||
# Regular expression which should only match correct module level names
|
||||
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__)|log)$
|
||||
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__)|log|urlpatterns)$
|
||||
|
||||
# Regular expression which should only match correct class names
|
||||
class-rgx=[A-Z_][a-zA-Z0-9]+$
|
||||
@@ -106,7 +110,7 @@ class-rgx=[A-Z_][a-zA-Z0-9]+$
|
||||
function-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match correct method names
|
||||
method-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
method-rgx=([a-z_][a-z0-9_]{2,60}|setUp|set[Uu]pClass|tearDown|tear[Dd]ownClass|assert[A-Z]\w*)$
|
||||
|
||||
# Regular expression which should only match correct instance attribute names
|
||||
attr-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
@@ -112,7 +112,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
self.assertTrue(sequential.location.url() in chapter.definition['children'])
|
||||
|
||||
self.client.post(reverse('delete_item'),
|
||||
json.dumps({'id': sequential.location.url(), 'delete_children':'true'}),
|
||||
json.dumps({'id': sequential.location.url(), 'delete_children':'true', 'delete_all_versions':'true'}),
|
||||
"application/json")
|
||||
|
||||
found = False
|
||||
|
||||
@@ -639,15 +639,15 @@ def delete_item(request):
|
||||
modulestore('direct').delete_item(item.location)
|
||||
|
||||
# cdodge: we need to remove our parent's pointer to us so that it is no longer dangling
|
||||
if delete_all_versions:
|
||||
parent_locs = modulestore('direct').get_parent_locations(item_loc, None)
|
||||
|
||||
parent_locs = modulestore('direct').get_parent_locations(item_loc, None)
|
||||
|
||||
for parent_loc in parent_locs:
|
||||
parent = modulestore('direct').get_item(parent_loc)
|
||||
item_url = item_loc.url()
|
||||
if item_url in parent.definition["children"]:
|
||||
parent.definition["children"].remove(item_url)
|
||||
modulestore('direct').update_children(parent.location, parent.definition["children"])
|
||||
for parent_loc in parent_locs:
|
||||
parent = modulestore('direct').get_item(parent_loc)
|
||||
item_url = item_loc.url()
|
||||
if item_url in parent.definition["children"]:
|
||||
parent.definition["children"].remove(item_url)
|
||||
modulestore('direct').update_children(parent.location, parent.definition["children"])
|
||||
|
||||
return HttpResponse()
|
||||
|
||||
@@ -1589,4 +1589,4 @@ def get_location_and_verify_access(request, org, course, name):
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
|
||||
return location
|
||||
return location
|
||||
|
||||
@@ -21,6 +21,32 @@ body.course.checklists {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
// visual status
|
||||
.viz-checklist-status {
|
||||
@include text-hide();
|
||||
@include size(100%,($baseline/4));
|
||||
position: relative;
|
||||
display: block;
|
||||
margin: 0;
|
||||
background: $gray-l4;
|
||||
|
||||
.viz-checklist-status-value {
|
||||
@include transition(width 2s ease-in-out .25s);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 0%;
|
||||
height: ($baseline/4);
|
||||
background: $green;
|
||||
|
||||
.int {
|
||||
@include text-sr();
|
||||
}
|
||||
}
|
||||
}
|
||||
// <span class="viz viz-checklist-status"><span class="viz value viz-checklist-status-value"><span class="int">0</span>% of checklist completed</span></span>
|
||||
|
||||
// header/title
|
||||
header {
|
||||
@include clearfix();
|
||||
@include box-shadow(inset 0 -1px 1px $shadow-l1);
|
||||
@@ -127,6 +153,13 @@ body.course.checklists {
|
||||
// state - completed
|
||||
&.is-completed {
|
||||
|
||||
.viz-checklist-status {
|
||||
|
||||
.viz-checklist-status-value {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
|
||||
.checklist-title, .icon-confirm {
|
||||
@@ -273,10 +306,20 @@ body.course.checklists {
|
||||
color: $gray-l2;
|
||||
}
|
||||
|
||||
.task-actions {
|
||||
|
||||
.action-primary {
|
||||
@include grey-button;
|
||||
@include font-size(12);
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $blue-l5;
|
||||
border-bottom-color: $blue-l4;
|
||||
border-top-color: $blue-l4;
|
||||
background: $gray-l5;
|
||||
border-bottom-color: $gray-l4;
|
||||
border-top-color: $gray-l4;
|
||||
|
||||
.task-details {
|
||||
opacity:1.0;
|
||||
|
||||
@@ -14,11 +14,17 @@
|
||||
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
<div class="introduction">
|
||||
<p class="copy">Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.<a href="" class="demo-checklistviz">Test Checklist Progress Visualization</a></p>
|
||||
</div>
|
||||
|
||||
<article class="content-primary" role="main">
|
||||
<form id="course-checklists" class="course-checklists" method="post" action="">
|
||||
<h2 class="title title-3 sr">Current Checklists</h2>
|
||||
|
||||
<section class="course-checklist" id="course-checklist1">
|
||||
<span class="viz viz-checklist-status"><span class="viz value viz-checklist-status-value"><span class="int">0</span>% of checklist completed</span></span>
|
||||
|
||||
<header>
|
||||
<h3 class="checklist-title title-2"><i class="ss-icon ss-symbolicons-standard icon-confirm">✓</i> Getting Started with Studio</h3>
|
||||
<span class="checklist-status status">Tasks Completed: <span class="status-count">0</span>/<span class="status-amount">4</span></span>
|
||||
@@ -100,6 +106,8 @@
|
||||
</section>
|
||||
|
||||
<section class="course-checklist" id="course-checklist2">
|
||||
<span class="viz viz-checklist-status"><span class="viz value viz-checklist-status-value"><span class="int">0</span>% of checklist completed</span></span>
|
||||
|
||||
<header>
|
||||
<h3 class="checklist-title title-2"><i class="ss-icon ss-symbolicons-standard icon-confirm">✓</i> Draft a Rough Course Outline</h3>
|
||||
<span class="checklist-status status">Tasks Completed: <span class="status-count">0</span>/<span class="status-amount">7</span></span>
|
||||
@@ -238,6 +246,8 @@
|
||||
</section>
|
||||
|
||||
<section class="course-checklist" id="course-checklist3">
|
||||
<span class="viz viz-checklist-status"><span class="viz value viz-checklist-status-value"><span class="int">0</span>% of checklist completed</span></span>
|
||||
|
||||
<header>
|
||||
<h3 class="checklist-title title-2"><i class="ss-icon ss-symbolicons-standard icon-confirm">✓</i> Explore edX's Support Tools</h3>
|
||||
<span class="checklist-status status">Tasks Completed: <span class="status-count">0</span>/<span class="status-amount">4</span></span>
|
||||
@@ -319,9 +329,10 @@
|
||||
</section>
|
||||
|
||||
<section class="course-checklist" id="course-checklist4">
|
||||
<span class="viz viz-checklist-status"><span class="viz value viz-checklist-status-value"><span class="int">0</span>% of checklist completed</span></span>
|
||||
|
||||
<header>
|
||||
<h3 class="checklist-title title-2"><i class="ss-icon ss-symbolicons-standard icon-confirm">✓</i> Draft your Course Introduction</h3>
|
||||
<span class="checklist-status status">Tasks Completed: <span class="status-count">0</span>/<span class="status-amount">4</span></span>
|
||||
</header>
|
||||
|
||||
<ul class="list list-tasks">
|
||||
@@ -330,7 +341,7 @@
|
||||
<input type="checkbox" class="task-input" name="course-checklist4-task1" id="course-checklist4-task1" value="course-checklist4-task1 complete">
|
||||
|
||||
<div class="task-details">
|
||||
<h4 class="task-name title title-3">Drafting a Course Description/h4>
|
||||
<h4 class="task-name title title-3">Drafting a Course Description</h4>
|
||||
<p class="task-description">Courses on edX each have their own introduction page, including a course video, description, and more. Draft the introduction students will read before deciding to enroll in your course.</p>
|
||||
</div>
|
||||
</label>
|
||||
@@ -342,7 +353,7 @@
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li class="task is-completed">
|
||||
<li class="task">
|
||||
<label for="course-checklist4-task2">
|
||||
<input type="checkbox" class="task-input" name="course-checklist4-task2" id="course-checklist4-task2" value="course-checklist4-task2 complete">
|
||||
|
||||
@@ -410,6 +421,8 @@
|
||||
<h2 class="title title-3 sr">Completed Checklists</h2>
|
||||
|
||||
<section class="course-checklist is-completed" id="course-checklist5">
|
||||
<span class="viz viz-checklist-status"><span class="viz value viz-checklist-status-value"><span class="int">0</span>% of checklist completed</span></span>
|
||||
|
||||
<header>
|
||||
<h3 class="checklist-title title-2"><i class="ss-icon ss-symbolicons-standard icon-confirm">✓</i> Completed Checklist Example</h3>
|
||||
<span class="checklist-status status">Tasks Completed: <span class="status-count">3</span>/<span class="status-amount">3</span></span>
|
||||
@@ -558,6 +571,12 @@
|
||||
$(this).bind('click', toggleTask);
|
||||
});
|
||||
|
||||
// demo/proof of concept for visual progress
|
||||
$('.demo-checklistviz').click(function(e){
|
||||
(e).preventDefault();
|
||||
$('#course-checklist1 .viz-checklist-status .viz-checklist-status-value').css('width','25%');
|
||||
});
|
||||
|
||||
function toggleChecklist(e) {
|
||||
(e).preventDefault();
|
||||
$(this).closest('.course-checklist').toggleClass('is-collapsed');
|
||||
|
||||
@@ -87,7 +87,7 @@ class FolditModule(XModule):
|
||||
from foldit.models import Score
|
||||
|
||||
leaders = [(e['username'], e['score']) for e in Score.get_tops_n(10)]
|
||||
leaders.sort(key=lambda x: x[1])
|
||||
leaders.sort(key=lambda x: -x[1])
|
||||
|
||||
return leaders
|
||||
|
||||
|
||||
@@ -32,10 +32,11 @@ if [ ! -d /mnt/virtualenvs/"$JOB_NAME" ]; then
|
||||
virtualenv /mnt/virtualenvs/"$JOB_NAME"
|
||||
fi
|
||||
|
||||
export PIP_DOWNLOAD_CACHE=/mnt/pip-cache
|
||||
|
||||
source /mnt/virtualenvs/"$JOB_NAME"/bin/activate
|
||||
pip install -q -r pre-requirements.txt
|
||||
pip install -q -r test-requirements.txt
|
||||
yes w | pip install -q -r requirements.txt
|
||||
yes w | pip install -q -r test-requirements.txt -r requirements.txt
|
||||
|
||||
rake clobber
|
||||
rake pep8
|
||||
|
||||
122
lms/djangoapps/courseware/management/commands/regrade_partial.py
Normal file
122
lms/djangoapps/courseware/management/commands/regrade_partial.py
Normal file
@@ -0,0 +1,122 @@
|
||||
'''
|
||||
This is a one-off command aimed at fixing a temporary problem encountered where partial credit was awarded for
|
||||
code problems, but the resulting score (or grade) was mistakenly set to zero because of a bug in
|
||||
CorrectMap.get_npoints().
|
||||
'''
|
||||
|
||||
import json
|
||||
import logging
|
||||
from optparse import make_option
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from courseware.models import StudentModule
|
||||
from capa.correctmap import CorrectMap
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
'''
|
||||
The fix here is to recalculate the score/grade based on the partial credit.
|
||||
To narrow down the set of problems that might need fixing, the StudentModule
|
||||
objects to be checked is filtered down to those:
|
||||
|
||||
created < '2013-03-08 15:45:00' (the problem must have been answered before the fix was installed,
|
||||
on Prod and Edge)
|
||||
modified > '2013-03-07 20:18:00' (the problem must have been visited after the bug was introduced)
|
||||
state like '%"npoints": 0.%' (the problem must have some form of partial credit).
|
||||
'''
|
||||
|
||||
num_visited = 0
|
||||
num_changed = 0
|
||||
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--save',
|
||||
action='store_true',
|
||||
dest='save_changes',
|
||||
default=False,
|
||||
help='Persist the changes that were encountered. If not set, no changes are saved.'), )
|
||||
|
||||
def fix_studentmodules(self, save_changes):
|
||||
'''Identify the list of StudentModule objects that might need fixing, and then fix each one'''
|
||||
modules = StudentModule.objects.filter(modified__gt='2013-03-07 20:18:00',
|
||||
created__lt='2013-03-08 15:45:00',
|
||||
state__contains='"npoints": 0.')
|
||||
|
||||
for module in modules:
|
||||
self.fix_studentmodule_grade(module, save_changes)
|
||||
|
||||
def fix_studentmodule_grade(self, module, save_changes):
|
||||
''' Fix the grade assigned to a StudentModule'''
|
||||
module_state = module.state
|
||||
if module_state is None:
|
||||
# not likely, since we filter on it. But in general...
|
||||
LOG.info("No state found for {type} module {id} for student {student} in course {course_id}"
|
||||
.format(type=module.module_type, id=module.module_state_key,
|
||||
student=module.student.username, course_id=module.course_id))
|
||||
return
|
||||
|
||||
state_dict = json.loads(module_state)
|
||||
self.num_visited += 1
|
||||
|
||||
# LoncapaProblem.get_score() checks student_answers -- if there are none, we will return a grade of 0
|
||||
# Check that this is the case, but do so sooner, before we do any of the other grading work.
|
||||
student_answers = state_dict['student_answers']
|
||||
if (not student_answers) or len(student_answers) == 0:
|
||||
# we should not have a grade here:
|
||||
if module.grade != 0:
|
||||
LOG.error("No answer found but grade {grade} exists for {type} module {id} for student {student} "
|
||||
"in course {course_id}".format(grade=module.grade,
|
||||
type=module.module_type, id=module.module_state_key,
|
||||
student=module.student.username, course_id=module.course_id))
|
||||
else:
|
||||
LOG.debug("No answer and no grade found for {type} module {id} for student {student} "
|
||||
"in course {course_id}".format(grade=module.grade,
|
||||
type=module.module_type, id=module.module_state_key,
|
||||
student=module.student.username, course_id=module.course_id))
|
||||
return
|
||||
|
||||
# load into a CorrectMap, as done in LoncapaProblem.__init__():
|
||||
correct_map = CorrectMap()
|
||||
if 'correct_map' in state_dict:
|
||||
correct_map.set_dict(state_dict['correct_map'])
|
||||
|
||||
# calculate score the way LoncapaProblem.get_score() works, by deferring to
|
||||
# CorrectMap's get_npoints implementation.
|
||||
correct = 0
|
||||
for key in correct_map:
|
||||
correct += correct_map.get_npoints(key)
|
||||
|
||||
if module.grade == correct:
|
||||
# nothing to change
|
||||
LOG.debug("Grade matches for {type} module {id} for student {student} in course {course_id}"
|
||||
.format(type=module.module_type, id=module.module_state_key,
|
||||
student=module.student.username, course_id=module.course_id))
|
||||
elif save_changes:
|
||||
# make the change
|
||||
LOG.info("Grade changing from {0} to {1} for {type} module {id} for student {student} "
|
||||
"in course {course_id}".format(module.grade, correct,
|
||||
type=module.module_type, id=module.module_state_key,
|
||||
student=module.student.username, course_id=module.course_id))
|
||||
module.grade = correct
|
||||
module.save()
|
||||
self.num_changed += 1
|
||||
else:
|
||||
# don't make the change, but log that the change would be made
|
||||
LOG.info("Grade would change from {0} to {1} for {type} module {id} for student {student} "
|
||||
"in course {course_id}".format(module.grade, correct,
|
||||
type=module.module_type, id=module.module_state_key,
|
||||
student=module.student.username, course_id=module.course_id))
|
||||
self.num_changed += 1
|
||||
|
||||
def handle(self, **options):
|
||||
'''Handle management command request'''
|
||||
|
||||
save_changes = options['save_changes']
|
||||
|
||||
LOG.info("Starting run: save_changes = {0}".format(save_changes))
|
||||
|
||||
self.fix_studentmodules(save_changes)
|
||||
|
||||
LOG.info("Finished run: updating {0} of {1} modules".format(self.num_changed, self.num_visited))
|
||||
@@ -1,61 +1,61 @@
|
||||
django==1.4.3
|
||||
pip
|
||||
numpy==1.6.2
|
||||
scipy==0.11.0
|
||||
Markdown==2.2.1
|
||||
pygments==1.5
|
||||
lxml==3.0.1
|
||||
boto==2.6.0
|
||||
mako==0.7.3
|
||||
python-memcached==1.48
|
||||
python-openid==2.2.5
|
||||
path.py
|
||||
django_debug_toolbar
|
||||
fs==0.4.0
|
||||
beautifulsoup==3.2.1
|
||||
-r repo-requirements.txt
|
||||
beautifulsoup4==4.1.3
|
||||
feedparser==5.1.3
|
||||
requests==0.14.2
|
||||
http://sympy.googlecode.com/files/sympy-0.7.1.tar.gz
|
||||
newrelic==1.8.0.13
|
||||
glob2==0.3
|
||||
pymongo==2.4.1
|
||||
django_nose==1.1
|
||||
nosexcover==1.0.7
|
||||
rednose==0.3.3
|
||||
GitPython==0.3.2.RC1
|
||||
mock==0.8.0
|
||||
PyYAML==3.10
|
||||
South==0.7.6
|
||||
pytz==2012h
|
||||
beautifulsoup==3.2.1
|
||||
boto==2.6.0
|
||||
django-celery==3.0.11
|
||||
django-countries==1.5
|
||||
django-kombu==0.9.4
|
||||
django-debug-toolbar-mongo
|
||||
django-followit==0.0.3
|
||||
django-jasmine==0.3.2
|
||||
django-keyedcache==1.4-6
|
||||
django-kombu==0.9.4
|
||||
django-mako==0.1.5pre
|
||||
django-masquerade==0.1.6
|
||||
django-mptt==0.5.5
|
||||
django-openid-auth==0.4
|
||||
django-robots==0.9.1
|
||||
django-sekizai==0.6.1
|
||||
django-ses==0.4.1
|
||||
django-storages==1.1.5
|
||||
django-threaded-multihost==1.4-1
|
||||
django-sekizai==0.6.1
|
||||
django-mptt==0.5.5
|
||||
sorl-thumbnail==11.12
|
||||
networkx==1.7
|
||||
pygraphviz==1.1
|
||||
-r repo-requirements.txt
|
||||
nltk==2.0.4
|
||||
django-debug-toolbar-mongo
|
||||
dogstatsd-python==0.2.1
|
||||
MySQL-python==1.2.4c1
|
||||
sphinx==1.1.3
|
||||
factory_boy
|
||||
Shapely==1.2.16
|
||||
ipython==0.13.1
|
||||
xmltodict==0.4.1
|
||||
paramiko==1.9.0
|
||||
Pillow==1.7.8
|
||||
django==1.4.3
|
||||
django_debug_toolbar
|
||||
django_nose==1.1
|
||||
dogapi==1.2.1
|
||||
dogstatsd-python==0.2.1
|
||||
factory_boy
|
||||
feedparser==5.1.3
|
||||
fs==0.4.0
|
||||
GitPython==0.3.2.RC1
|
||||
glob2==0.3
|
||||
http://sympy.googlecode.com/files/sympy-0.7.1.tar.gz
|
||||
ipython==0.13.1
|
||||
lxml==3.0.1
|
||||
mako==0.7.3
|
||||
Markdown==2.2.1
|
||||
mock==0.8.0
|
||||
MySQL-python==1.2.4c1
|
||||
networkx==1.7
|
||||
newrelic==1.8.0.13
|
||||
nltk==2.0.4
|
||||
nosexcover==1.0.7
|
||||
numpy==1.6.2
|
||||
paramiko==1.9.0
|
||||
path.py
|
||||
Pillow==1.7.8
|
||||
pip
|
||||
pygments==1.5
|
||||
pygraphviz==1.1
|
||||
pymongo==2.4.1
|
||||
python-memcached==1.48
|
||||
python-openid==2.2.5
|
||||
pytz==2012h
|
||||
PyYAML==3.10
|
||||
rednose==0.3.3
|
||||
requests==0.14.2
|
||||
scipy==0.11.0
|
||||
Shapely==1.2.16
|
||||
sorl-thumbnail==11.12
|
||||
South==0.7.6
|
||||
sphinx==1.1.3
|
||||
xmltodict==0.4.1
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
django-nose
|
||||
coverage
|
||||
nosexcover
|
||||
pylint
|
||||
pep8
|
||||
lettuce
|
||||
selenium
|
||||
factory_boy
|
||||
splinter
|
||||
|
||||
|
||||
Reference in New Issue
Block a user