Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions cms/djangoapps/contentstore/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Common utility functions useful throughout the contentstore
"""

from collections import defaultdict
import logging
from contextlib import contextmanager
from datetime import datetime
Expand Down Expand Up @@ -731,3 +732,24 @@ def translation_language(language):
translation.activate(previous)
else:
yield


def get_subsections_by_assignment_type(course_key):
"""
Construct a dictionary mapping each found assignment type in the course
to a list of dictionaries with the display name of the subsection and
the display name of the section they are in
"""
subsections_by_assignment_type = defaultdict(list)

with modulestore().bulk_operations(course_key):
course = modulestore().get_course(course_key, depth=3)
sections = course.get_children()
for section in sections:
subsections = section.get_children()
for subsection in subsections:
if subsection.format:
subsections_by_assignment_type[subsection.format].append(
f'{section.display_name} - {subsection.display_name}'
)
return subsections_by_assignment_type
3 changes: 3 additions & 0 deletions cms/djangoapps/contentstore/views/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
add_instructor,
get_lms_link_for_item,
get_proctored_exam_settings_url,
get_subsections_by_assignment_type,
initialize_permissions,
remove_all_instructors,
reverse_course_url,
Expand Down Expand Up @@ -1343,13 +1344,15 @@ def grading_handler(request, course_key_string, grader_index=None):

if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET':
course_details = CourseGradingModel.fetch(course_key)
course_assignment_lists = get_subsections_by_assignment_type(course_key)
return render_to_response('settings_graders.html', {
'context_course': course_block,
'course_locator': course_key,
'course_details': course_details,
'grading_url': reverse_course_url('grading_handler', course_key),
'is_credit_course': is_credit_course(course_key),
'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_block.id),
'course_assignment_lists': dict(course_assignment_lists)
})
elif 'application/json' in request.META.get('HTTP_ACCEPT', ''):
if request.method == 'GET':
Expand Down
5 changes: 3 additions & 2 deletions cms/static/js/factories/settings_graders.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ define([
'jquery', 'js/views/settings/grading', 'js/models/settings/course_grading_policy'
], function($, GradingView, CourseGradingPolicyModel) {
'use strict';
return function(courseDetails, gradingUrl) {
return function(courseDetails, gradingUrl, courseAssignmentLists) {
var model, editor;

$('form :input')
Expand All @@ -17,7 +17,8 @@ define([
model.urlRoot = gradingUrl;
editor = new GradingView({
el: $('.settings-grading'),
model: model
model: model,
courseAssignmentLists: courseAssignmentLists
});
editor.render();
};
Expand Down
4 changes: 3 additions & 1 deletion cms/static/js/models/settings/course_grading_policy.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ define(['backbone', 'js/models/location', 'js/collections/course_grader', 'edx-u
graders: null, // CourseGraderCollection
grade_cutoffs: null, // CourseGradeCutoff model
grace_period: null, // either null or { hours: n, minutes: m, ...}
minimum_grade_credit: null // either null or percentage
minimum_grade_credit: null, // either null or percentage
assignment_count_info: [], // Object with keys mapping assignment type names to a list of
//assignment display names
},
parse: function(attributes) {
if (attributes.graders) {
Expand Down
25 changes: 21 additions & 4 deletions cms/static/js/views/settings/grading.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function(ValidatingView, _, $, ui, GraderView, StringUtils, HtmlUtils) {
'focus :input': 'inputFocus',
'blur :input': 'inputUnfocus'
},
initialize: function() {
initialize: function(options) {
// load template for grading view
var self = this;
this.template = HtmlUtils.template(
Expand All @@ -40,6 +40,7 @@ function(ValidatingView, _, $, ui, GraderView, StringUtils, HtmlUtils) {
this.model.get('graders').on('reset', this.render, this);
this.model.get('graders').on('add', this.render, this);
this.selectorToField = _.invert(this.fieldToSelectorMap);
this.courseAssignmentLists = options.courseAssignmentLists;
this.render();
},

Expand Down Expand Up @@ -73,10 +74,26 @@ function(ValidatingView, _, $, ui, GraderView, StringUtils, HtmlUtils) {
},
this);
gradeCollection.each(function(gradeModel) {
HtmlUtils.append(gradelist, self.template({model: gradeModel}));
var graderType = gradeModel.get('type');
var graderTypeAssignmentList = self.courseAssignmentLists[graderType]
if (graderTypeAssignmentList === undefined) {
graderTypeAssignmentList = [];
}

HtmlUtils.append(
gradelist,
self.template({
model: gradeModel,
assignmentList: graderTypeAssignmentList
})
);
var newEle = gradelist.children().last();
var newView = new GraderView({el: newEle,
model: gradeModel, collection: gradeCollection});
var newView = new GraderView({
el: newEle,
model: gradeModel,
collection: gradeCollection,
courseAssignmentCountInfo: self.courseAssignmentCountInfo,
});
// Listen in order to rerender when the 'cancel' button is
// pressed
self.listenTo(newView, 'revert', _.bind(self.render, self));
Expand Down
32 changes: 32 additions & 0 deletions cms/static/sass/views/_settings.scss
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,38 @@
#field-course-grading-assignment-droppable {
width: flex-grid(2, 6);
}

.assignment-count-info {
padding: $baseline;
border-radius: 3px;
@extend %t-copy-sub2;
}

.assignment-count-warning {
background-color: $yellow-l4;

.assignment-count-warning-header{
@extend %t-copy-sub1;
font-weight: bold;
.header-warning {
font-weight: bolder;
}
margin-bottom: ($baseline / 2);
}

.assignment-count-warning-content {

ol.assignment_type_count_list {
list-style: auto;
list-style-position: inside;
padding-left: ($baseline*1.5);
}
}
}

.assignment-count-matches {
background-color: $green-l4;
}
}

.actions {
Expand Down
47 changes: 47 additions & 0 deletions cms/templates/js/course_grade_policy.underscore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,53 @@
<span class="tip tip-stacked"><%- gettext("The number of assignments of this type that will be dropped. The lowest scoring assignments are dropped first.") %></span>
</div>

<% if (model.get('type') !== '') { %>
<% if (assignmentList.length !== model.get('min_count')){ %>
<div class="assignment-count-info assignment-count-warning">
<div class="assignment-count-warning-header">
<span class="icon fa fa-exclamation-circle" aria-hidden="true"></span>
<span class="header-warning"><%- gettext("Warning: ") %></span>
<%-
edx.StringUtils.interpolate(
gettext("The number of {type} assignments defined here does not match the current number of {type} assignments in the course:"),
{type: model.get('type')},
)
%>
</div>
<div class="assignment-count-warning-content">
<% if (assignmentList.length == 0){ %>
<div><%- gettext("There are no assignments of this type in the course.") %></div>
<% } else { %>
<%-
edx.StringUtils.interpolate(
gettext("{assignment_count} {type} assignment(s) found:"),
{
assignment_count: assignmentList.length,
type: model.get('type')
},
)
%>
<ol class="assignment_type_count_list">
<% _.each(assignmentList, function (qualifiedSubsectionName){ %>
<li><%- qualifiedSubsectionName %></li>
<% }) %>
</ol>
<% } %>
</div>
</div>
<% } else { %>
<div class="assignment-count-info assignment-count-matches">
<span class="icon fa fa-check-circle-o" aria-hidden="true"></span>
<%-
edx.StringUtils.interpolate(
gettext("The number of {type} assignments in the course matches the number defined here."),
{type: model.get('type')},
)
%>
</div>
<% } %>
<% } %>

<div class="actions">
<a href="#" class="button delete-button standard remove-item remove-grading-data"><span class="delete-icon"></span><%- gettext("Delete") %></a>
</div>
Expand Down
9 changes: 6 additions & 3 deletions cms/templates/settings_graders.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@
<%block name="requirejs">
require(["js/factories/settings_graders"], function(SettingsGradersFactory) {
SettingsGradersFactory(
_.extend(${dump_js_escaped_json(course_details, cls=CourseSettingsEncoder) | n, decode.utf8},
{is_credit_course: ${is_credit_course | n, dump_js_escaped_json}}),
"${grading_url | n, js_escaped_string}"
_.extend(
${dump_js_escaped_json(course_details, cls=CourseSettingsEncoder) | n, decode.utf8},
{ is_credit_course: ${is_credit_course | n, dump_js_escaped_json} }
),
"${grading_url | n, js_escaped_string}",
${course_assignment_lists | n, dump_js_escaped_json},
);
});
</%block>
Expand Down