diff --git a/common/test/acceptance/tests/lms/test_lms_instructor_dashboard.py b/common/test/acceptance/tests/lms/test_lms_instructor_dashboard.py index 1b2e3e9a21..4b75fc6506 100644 --- a/common/test/acceptance/tests/lms/test_lms_instructor_dashboard.py +++ b/common/test/acceptance/tests/lms/test_lms_instructor_dashboard.py @@ -737,7 +737,7 @@ class CertificatesTest(BaseInstructorDashboardTest): self.certificates_section.add_certificate_exception(self.user_name, '') self.assertIn( - 'User (username/email={user}) already in exception list.'.format(user=self.user_name), + '{user} already in exception list.'.format(user=self.user_name), self.certificates_section.message.text ) @@ -784,8 +784,7 @@ class CertificatesTest(BaseInstructorDashboardTest): self.certificates_section.wait_for_ajax() self.assertIn( - "We can't find the user (username/email={user}) you've entered. " - "Make sure the username or email address is correct, then try again.".format(user=invalid_user), + "{user} does not exist in the LMS. Please check your spelling and retry.".format(user=invalid_user), self.certificates_section.message.text ) @@ -818,8 +817,7 @@ class CertificatesTest(BaseInstructorDashboardTest): self.certificates_section.wait_for_ajax() self.assertIn( - "The user (username/email={user}) you have entered is not enrolled in this course. " - "Make sure the username or email address is correct, then try again.".format(user=new_user), + "{user} is not enrolled in this course. Please check your spelling and retry.".format(user=new_user), self.certificates_section.message.text ) @@ -840,6 +838,7 @@ class CertificatesTest(BaseInstructorDashboardTest): self.certificates_section.wait_for_ajax() self.assertIn( - 'Certificate generation started for white listed students.', + self.user_name + ' has been successfully added to the exception list. Click Generate Exception Certificate' + ' below to send the certificate.', self.certificates_section.message.text ) diff --git a/lms/djangoapps/instructor/tests/test_certificates.py b/lms/djangoapps/instructor/tests/test_certificates.py index 2eb17e1e42..7f74b2b144 100644 --- a/lms/djangoapps/instructor/tests/test_certificates.py +++ b/lms/djangoapps/instructor/tests/test_certificates.py @@ -474,8 +474,7 @@ class CertificateExceptionViewInstructorApiTest(SharedModuleStoreTestCase): # Assert Error Message self.assertEqual( res_json['message'], - u"We can't find the user (username/email={user}) you've entered. " - u"Make sure the username or email address is correct, then try again.".format(user=invalid_user) + u"{user} does not exist in the LMS. Please check your spelling and retry.".format(user=invalid_user) ) def test_certificate_exception_missing_username_and_email_error(self): @@ -501,7 +500,7 @@ class CertificateExceptionViewInstructorApiTest(SharedModuleStoreTestCase): self.assertEqual( res_json['message'], u'Student username/email field is required and can not be empty. ' - u'Kindly fill in username/email and then press "Add Exception" button.' + u'Kindly fill in username/email and then press "Add to Exception List" button.' ) def test_certificate_exception_duplicate_user_error(self): @@ -591,8 +590,7 @@ class CertificateExceptionViewInstructorApiTest(SharedModuleStoreTestCase): # Assert Error Message self.assertEqual( res_json['message'], - "The user (username/email={user}) you have entered is not enrolled in this course. " - "Make sure the username or email address is correct, then try again.".format( + "{user} is not enrolled in this course. Please check your spelling and retry.".format( user=self.certificate_exception['user_name'] ) ) @@ -646,7 +644,7 @@ class CertificateExceptionViewInstructorApiTest(SharedModuleStoreTestCase): # Assert Error Message self.assertEqual( res_json['message'], - u"Invalid Json data, Please refresh the page and then try again." + u"The record is not in the correct format. Please add a valid username or email address." ) def test_remove_certificate_exception_non_existing_error(self): diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index f688923477..53035e8e69 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -2839,22 +2839,21 @@ def parse_request_data_and_get_user(request, course_key): try: certificate_exception = json.loads(request.body or '{}') except ValueError: - raise ValueError(_('Invalid Json data, Please refresh the page and then try again.')) + raise ValueError(_('The record is not in the correct format. Please add a valid username or email address.')) user = certificate_exception.get('user_name', '') or certificate_exception.get('user_email', '') if not user: raise ValueError(_('Student username/email field is required and can not be empty. ' - 'Kindly fill in username/email and then press "Add Exception" button.')) + 'Kindly fill in username/email and then press "Add to Exception List" button.')) try: db_user = get_user_by_username_or_email(user) except ObjectDoesNotExist: - raise ValueError(_("We can't find the user (username/email={user}) you've entered. " - "Make sure the username or email address is correct, then try again.").format(user=user)) + raise ValueError(_("{user} does not exist in the LMS. Please check your spelling and retry.").format(user=user)) # Make Sure the given student is enrolled in the course if not CourseEnrollment.is_enrolled(db_user, course_key): - raise ValueError(_("The user (username/email={user}) you have entered is not enrolled in this course. " - "Make sure the username or email address is correct, then try again.").format(user=user)) + raise ValueError(_("{user} is not enrolled in this course. Please check your spelling and retry.") + .format(user=user)) return certificate_exception, db_user diff --git a/lms/static/js/certificates/views/certificate_bulk_whitelist.js b/lms/static/js/certificates/views/certificate_bulk_whitelist.js index 4d340f36f4..73e6bc6585 100644 --- a/lms/static/js/certificates/views/certificate_bulk_whitelist.js +++ b/lms/static/js/certificates/views/certificate_bulk_whitelist.js @@ -32,7 +32,8 @@ el: DOM_SELECTORS.bulk_exception, events: { 'change #browseBtn': 'chooseFile', - 'click .upload-csv-button': 'uploadCSV' + 'click .upload-csv-button': 'uploadCSV', + 'click a.arrow': 'toggleMessageDetails' }, initialize: function(options){ @@ -72,19 +73,22 @@ }, display_response: function(data_from_server) { - $(".results").empty(); + $(".bulk-exception-results").removeClass('hidden').empty(); // Display general error messages if (data_from_server.general_errors.length) { var errors = data_from_server.general_errors; - generate_div('msg-error', MESSAGE_GROUP.general_errors, gettext('Errors!'), errors); + generate_div( + MESSAGE_GROUP.general_errors, + gettext('Uploaded file issues. Click on "+" to view.'), + errors + ); } // Display success message if (data_from_server.success.length) { var success_data = data_from_server.success; generate_div( - 'msg-success', MESSAGE_GROUP.successfully_added, get_text(success_data.length, MESSAGE_GROUP.successfully_added), success_data @@ -98,7 +102,6 @@ if (row_errors.data_format_error.length) { var format_errors = row_errors.data_format_error; generate_div( - 'msg-error', MESSAGE_GROUP.data_format_error, get_text(format_errors.length, MESSAGE_GROUP.data_format_error), format_errors @@ -107,7 +110,6 @@ if (row_errors.user_not_exist.length) { var user_not_exist = row_errors.user_not_exist; generate_div( - 'msg-error', MESSAGE_GROUP.user_not_exist, get_text(user_not_exist.length, MESSAGE_GROUP.user_not_exist), user_not_exist @@ -116,7 +118,6 @@ if (row_errors.user_already_white_listed.length) { var user_already_white_listed = row_errors.user_already_white_listed; generate_div( - 'msg-error', MESSAGE_GROUP.user_already_white_listed, get_text(user_already_white_listed.length, MESSAGE_GROUP.user_already_white_listed), user_already_white_listed @@ -125,7 +126,6 @@ if (row_errors.user_not_enrolled.length) { var user_not_enrolled = row_errors.user_not_enrolled; generate_div( - 'msg-error', MESSAGE_GROUP.user_not_enrolled, get_text(user_not_enrolled.length, MESSAGE_GROUP.user_not_enrolled), user_not_enrolled @@ -133,17 +133,22 @@ } } - function generate_div(div_class, group, heading, display_data) { + function generate_div(group, heading, display_data) { // inner function generate div and display response messages. $('
', { - class: 'message ' + div_class + ' ' + group - }).appendTo('.results').prepend( "" + heading + "" ); + class: 'message ' + group + }).appendTo('.bulk-exception-results').prepend( + " + " + heading + ).append($('"+ gettext(message) + "
").focus(); + $(this.message_div).fadeOut(6000, "linear"); }, showSuccess: function(caller_object){ return function(xhr){ - caller_object.showMessage(xhr.message, 'msg-success'); + caller_object.showMessage(xhr.message); }; }, @@ -74,12 +88,11 @@ return function(xhr){ try{ var response = JSON.parse(xhr.responseText); - caller_object.showMessage(response.message, 'msg-error'); + caller_object.showMessage(response.message); } catch(exception){ caller_object.showMessage( - "Server Error, Please refresh the page and try again.", 'msg-error' - ); + "Server Error, Please refresh the page and try again."); } }; } diff --git a/lms/static/js/certificates/views/certificate_whitelist_editor.js b/lms/static/js/certificates/views/certificate_whitelist_editor.js index f979a54aa8..3c3ec763a3 100644 --- a/lms/static/js/certificates/views/certificate_whitelist_editor.js +++ b/lms/static/js/certificates/views/certificate_whitelist_editor.js @@ -19,11 +19,6 @@ 'click #add-exception': 'addException' }, - initialize: function(){ - this.on('removeException', this.removeException); - }, - - render: function(){ var template = this.loadTemplate('certificate-white-list-editor'); this.$el.html(template()); @@ -59,8 +54,7 @@ if(this.collection.findWhere(model)){ this.showMessage( - "User (username/email=" + (user_name || user_email) + ") already in exception list.", - 'msg-error' + (user_name || user_email) + " already in exception list." ); } else if(certificate_exception.isValid()){ @@ -70,7 +64,8 @@ success: this.showSuccess( this, true, - 'Student added to Certificate white list successfully.' + (user_name || user_email) + ' has been successfully added to the exception list.' + + ' Click Generate Exception Certificate below to send the certificate.' ), error: this.showError(this) } @@ -78,34 +73,7 @@ } else{ - this.showMessage(certificate_exception.validationError, 'msg-error'); - } - }, - - removeException: function(certificate){ - var model = this.collection.findWhere(certificate); - if(model){ - model.destroy( - { - success: this.showSuccess( - this, - false, - 'Student Removed from certificate white list successfully.' - ), - error: this.showError(this), - wait: true, - //emulateJSON: true, - data: JSON.stringify(model.attributes) - } - ); - this.showMessage('Exception is being removed from server.', 'msg-success'); - } - else{ - this.showMessage( - 'Could not find Certificate Exception in white list. ' + - 'Please refresh the page and try again', - 'msg-error' - ); + this.showMessage(certificate_exception.validationError); } }, @@ -114,12 +82,9 @@ return re.test(email); }, - showMessage: function(message, messageClass){ - this.$(this.message_div).text(message). - removeClass('msg-error msg-success').addClass(messageClass).focus(); - $('html, body').animate({ - scrollTop: this.$el.offset().top - 20 - }, 1000); + showMessage: function(message){ + $(this.message_div + ">p" ).remove(); + this.$(this.message_div).removeClass('hidden').append(""+ gettext(message) + "
"); }, showSuccess: function(caller, add_model, message){ @@ -127,7 +92,7 @@ if(add_model){ caller.collection.add(model); } - caller.showMessage(message, 'msg-success'); + caller.showMessage(message); }; }, @@ -135,11 +100,11 @@ return function(model, response){ try{ var response_data = JSON.parse(response.responseText); - caller.showMessage(response_data.message, 'msg-error'); + caller.showMessage(response_data.message); } catch(exception){ caller.showMessage("" + - "Server Error, Please refresh the page and try again.", 'msg-error' + "Server Error, Please refresh the page and try again." ); } }; diff --git a/lms/static/js/spec/instructor_dashboard/certificates_exception_spec.js b/lms/static/js/spec/instructor_dashboard/certificates_exception_spec.js index da76c17ec9..94bd191346 100644 --- a/lms/static/js/spec/instructor_dashboard/certificates_exception_spec.js +++ b/lms/static/js/spec/instructor_dashboard/certificates_exception_spec.js @@ -330,16 +330,15 @@ define([ it("verifies success and error messages", function() { var message_selector='.message', - error_class = 'msg-error', - success_class = 'msg-success', - success_message = 'Student added to Certificate white list successfully.', + success_message = 'test_user has been successfully added to the exception list. Click Generate' + + ' Exception Certificate below to send the certificate.', requests = AjaxHelpers.requests(this), duplicate_user='test_user'; var error_messages = { empty_user_name_email: 'Student username/email field is required and can not be empty. ' + 'Kindly fill in username/email and then press "Add Exception" button.', - duplicate_user: "User (username/email=" + (duplicate_user) + ") already in exception list." + duplicate_user: "" + (duplicate_user) + " already in exception list.
" }; // click 'Add Exception' button with empty username/email field @@ -347,7 +346,6 @@ define([ view.$el.find('#add-exception').click(); // Verify error message for missing username/email - expect(view.$el.find(message_selector)).toHaveClass(error_class); expect(view.$el.find(message_selector).html()).toMatch(error_messages.empty_user_name_email); // Add a new Exception to list @@ -369,7 +367,6 @@ define([ ); // Verify success message - expect(view.$el.find(message_selector)).toHaveClass(success_class); expect(view.$el.find(message_selector).html()).toMatch(success_message); // Add a duplicate Certificate Exception @@ -378,7 +375,6 @@ define([ view.$el.find('#add-exception').click(); // Verify success message - expect(view.$el.find(message_selector)).toHaveClass(error_class); expect(view.$el.find(message_selector).html()).toEqual(error_messages.duplicate_user); }); diff --git a/lms/static/sass/course/instructor/_instructor_2.scss b/lms/static/sass/course/instructor/_instructor_2.scss index 904e3b1982..06e77e73ff 100644 --- a/lms/static/sass/course/instructor/_instructor_2.scss +++ b/lms/static/sass/course/instructor/_instructor_2.scss @@ -1802,15 +1802,6 @@ input[name="subject"] { width: 75%; } - .certificate-exception-container { - h3 { - border-bottom: 1px groove black; - display: inline-block; - } - p.under-heading-text { - margin: 12px 0 12px 0; - } - } } input[name="subject"] { @@ -2110,103 +2101,174 @@ input[name="subject"] { @include right(auto); } -#certificate-white-list-editor{ - .certificate-exception-inputs{ - .student-username-or-email{ - width: 300px; - } - .notes-field{ - width: 400px; - } - p+p{ - margin-top: 5px; - } - .message{ - margin-top: 10px; +// view - certificates +// -------------------- +.instructor-dashboard-wrapper-2 section.idash-section#certificates { + + %btn-blue { + @extend %btn-primary-blue; + padding: ($baseline/2.5) ($baseline/2); + text-shadow: none; + margin-bottom: 10px; + } + + %exception-message { + margin-top: 15px; + background-color: $gray-l4; + border-top-style: groove; + color: $black; + } + + #certificate-white-list-editor { + .certificate-exception-inputs { + .student-username-or-email { + width: 300px; + margin-bottom: 10px; + } + .notes-field { + width: 400px; + } + p + p { + margin-top: 5px; + } + .message { + @extend %exception-message; + } + + .button-blue { + @extend %btn-blue; + } } } -} -.white-listed-students { + .white-listed-students { + margin-top: 10px; + table { + width: 100%; + word-wrap: break-word; - table { - width: 100%; - word-wrap: break-word; + th { + @extend %t-copy-sub2; + background-color: $gray-l5; + padding: ($baseline*0.75) ($baseline/2) ($baseline*0.75) ($baseline/2); + vertical-align: middle; + text-align: left; + color: $gray; - th { - @extend %t-copy-sub2; - background-color: $gray-l5; - padding: ($baseline*0.75) ($baseline/2) ($baseline*0.75) ($baseline/2); - vertical-align: middle; - text-align: left; - color: $gray; - - &.date, &.email{ - width: 230px; - } - - &.user-id{ - width: 60px; - } - - &.user-name{ - width: 150px; - } - - &.action{ - width: 150px; - } - - } - - td { - padding: ($baseline/2); - vertical-align: middle; - text-align: left; - } - - tbody { - box-shadow: 0 2px 2px $shadow-l1; - border: 1px solid $gray-l4; - background: $white; - - tr { - @include transition(all $tmg-f2 ease-in-out 0s); - border-top: 1px solid $gray-l4; - - &:first-child { - border-top: none; + &.date, &.email { + width: 230px; } - &:nth-child(odd) { - background-color: $gray-l6; + &.user-id { + width: 60px; } - a { - color: $gray-d1; + &.user-name { + width: 150px; + } + + &.action { + width: 150px; + } + + } + + td { + padding: ($baseline/2); + vertical-align: middle; + text-align: left; + } + + tbody { + box-shadow: 0 2px 2px $shadow-l1; + border: 1px solid $gray-l4; + background: $white; + + tr { + @include transition(all $tmg-f2 ease-in-out 0s); + border-top: 1px solid $gray-l4; + + &:first-child { + border-top: none; + } + + &:nth-child(odd) { + background-color: $gray-l6; + } + + a { + color: $gray-d1; + + &:hover { + color: $blue; + } + } &:hover { - color: $blue; + background-color: $blue-l5; } } - - &:hover { - background-color: $blue-l5; - } } } + + .button-blue { + @extend %btn-blue; + } + + .message { + @extend %exception-message; + } } + + .certificate-exception-container { + h3 { + border-bottom: 1px groove black; + display: inline-block; + } + p.under-heading { + margin: 12px 0 12px 0; + line-height: 23px; + } + } + + .bulk-white-list-exception { + margin-top: 10px; + .white-list-csv { + .bulk-exception-results { + margin-top: 10px; + background-color: $gray-l4; + border-top-style: groove; + color: $black; + margin-bottom: 15px; + + .message { + padding: 5px 10px; + margin: 2px; + } + + } + .arrow { + font-weight: bold; + } + + .button-blue { + @extend %btn-blue; + } + } + } + } + // Custom File upload .customBrowseBtn { margin: ($baseline/2) 0; display: inline-block; .file-browse { - position:relative; - overflow:hidden; + position: relative; + overflow: hidden; display: inline; @include margin-left(-5px); - span.browse{ + span.browse { @include button(simple, $blue); @include margin-right($baseline); padding: 6px ($baseline/2); @@ -2214,17 +2276,17 @@ input[name="subject"] { border-radius: 0 3px 3px 0; } input.file_field { - position:absolute; + position: absolute; @include right(0); - top:0; - margin:0; - padding:0; - cursor:pointer; - opacity:0; - filter:alpha(opacity=0); + top: 0; + margin: 0; + padding: 0; + cursor: pointer; + opacity: 0; + filter: alpha(opacity=0); } } - & > span, & input[disabled]{ + & > span, & input[disabled] { vertical-align: middle; } input[disabled] { @@ -2233,4 +2295,4 @@ input[name="subject"] { border: 1px solid $lightGrey1; cursor: not-allowed; } -} +} \ No newline at end of file diff --git a/lms/templates/instructor/instructor_dashboard_2/certificate-bulk-white-list.underscore b/lms/templates/instructor/instructor_dashboard_2/certificate-bulk-white-list.underscore index 419e9b112e..79a794f425 100644 --- a/lms/templates/instructor/instructor_dashboard_2/certificate-bulk-white-list.underscore +++ b/lms/templates/instructor/instructor_dashboard_2/certificate-bulk-white-list.underscore @@ -1,7 +1,7 @@- <%= gettext("You can upload a CSV file of usernames or email addresses to be added to the certificate exceptions white list.") %> +
+ <%= gettext("Upload a comma separated values (.csv) file that contains the usernames or email addresses of learners who have been given exceptions. Include the username or email address in the first comma separated field. You can include an optional note describing the reason for the exception in the second comma separated field.") %>
<%= gettext("You can add a username or email address to be added to the certificate exceptions white list.") %>
+<%= gettext("Enter the username or email address of each learner that you want to add as an exception.") %>
-
-
+
<%- gettext("No results") %>
@@ -37,4 +39,5 @@ <% } %> + <% } %> diff --git a/lms/templates/instructor/instructor_dashboard_2/certificates.html b/lms/templates/instructor/instructor_dashboard_2/certificates.html index 5e78fa8ff2..20a0f142bb 100644 --- a/lms/templates/instructor/instructor_dashboard_2/certificates.html +++ b/lms/templates/instructor/instructor_dashboard_2/certificates.html @@ -117,14 +117,15 @@ import json- ${_("Use this to generate certificates for users who did not pass the course but have been given an exception by the Course Team to earn a certificate.")} +
+ ${_("Set exceptions to generate certificates for learners who did not qualify for a certificate but have " \ + "been given an exception by the course team. After you add learners to the exception list, click Generate " \ + "Exception Certificates below.")}