Commit 3134a761 by Saleem Latif

Revised Generate Certificates Section and added Certificate Generation history UI.

parent 1f2f3de4
...@@ -673,7 +673,6 @@ class CertificatesTest(BaseInstructorDashboardTest): ...@@ -673,7 +673,6 @@ class CertificatesTest(BaseInstructorDashboardTest):
self.certificates_section.add_certificate_exception(self.user_name, notes) self.certificates_section.add_certificate_exception(self.user_name, notes)
self.assertIn(self.user_name, self.certificates_section.last_certificate_exception.text) self.assertIn(self.user_name, self.certificates_section.last_certificate_exception.text)
self.assertIn(notes, self.certificates_section.last_certificate_exception.text) self.assertIn(notes, self.certificates_section.last_certificate_exception.text)
self.assertIn(str(self.user_id), self.certificates_section.last_certificate_exception.text)
# Verify that added exceptions are also synced with backend # Verify that added exceptions are also synced with backend
# Revisit Page # Revisit Page
...@@ -685,7 +684,6 @@ class CertificatesTest(BaseInstructorDashboardTest): ...@@ -685,7 +684,6 @@ class CertificatesTest(BaseInstructorDashboardTest):
# validate certificate exception synced with server is visible in certificate exceptions list # validate certificate exception synced with server is visible in certificate exceptions list
self.assertIn(self.user_name, self.certificates_section.last_certificate_exception.text) self.assertIn(self.user_name, self.certificates_section.last_certificate_exception.text)
self.assertIn(notes, self.certificates_section.last_certificate_exception.text) self.assertIn(notes, self.certificates_section.last_certificate_exception.text)
self.assertIn(str(self.user_id), self.certificates_section.last_certificate_exception.text)
def test_instructor_can_remove_certificate_exception(self): def test_instructor_can_remove_certificate_exception(self):
""" """
...@@ -701,13 +699,11 @@ class CertificatesTest(BaseInstructorDashboardTest): ...@@ -701,13 +699,11 @@ class CertificatesTest(BaseInstructorDashboardTest):
self.certificates_section.add_certificate_exception(self.user_name, notes) self.certificates_section.add_certificate_exception(self.user_name, notes)
self.assertIn(self.user_name, self.certificates_section.last_certificate_exception.text) self.assertIn(self.user_name, self.certificates_section.last_certificate_exception.text)
self.assertIn(notes, self.certificates_section.last_certificate_exception.text) self.assertIn(notes, self.certificates_section.last_certificate_exception.text)
self.assertIn(str(self.user_id), self.certificates_section.last_certificate_exception.text)
# Remove Certificate Exception # Remove Certificate Exception
self.certificates_section.remove_first_certificate_exception() self.certificates_section.remove_first_certificate_exception()
self.assertNotIn(self.user_name, self.certificates_section.last_certificate_exception.text) self.assertNotIn(self.user_name, self.certificates_section.last_certificate_exception.text)
self.assertNotIn(notes, self.certificates_section.last_certificate_exception.text) self.assertNotIn(notes, self.certificates_section.last_certificate_exception.text)
self.assertNotIn(str(self.user_id), self.certificates_section.last_certificate_exception.text)
# Verify that added exceptions are also synced with backend # Verify that added exceptions are also synced with backend
# Revisit Page # Revisit Page
...@@ -719,7 +715,6 @@ class CertificatesTest(BaseInstructorDashboardTest): ...@@ -719,7 +715,6 @@ class CertificatesTest(BaseInstructorDashboardTest):
# validate certificate exception synced with server is visible in certificate exceptions list # validate certificate exception synced with server is visible in certificate exceptions list
self.assertNotIn(self.user_name, self.certificates_section.last_certificate_exception.text) self.assertNotIn(self.user_name, self.certificates_section.last_certificate_exception.text)
self.assertNotIn(notes, self.certificates_section.last_certificate_exception.text) self.assertNotIn(notes, self.certificates_section.last_certificate_exception.text)
self.assertNotIn(str(self.user_id), self.certificates_section.last_certificate_exception.text)
def test_error_on_duplicate_certificate_exception(self): def test_error_on_duplicate_certificate_exception(self):
""" """
......
...@@ -45,7 +45,6 @@ Eligibility: ...@@ -45,7 +45,6 @@ Eligibility:
then the student will be issued a certificate regardless of his grade, then the student will be issued a certificate regardless of his grade,
unless he has allow_certificate set to False. unless he has allow_certificate set to False.
""" """
from datetime import datetime
import json import json
import logging import logging
import uuid import uuid
...@@ -88,6 +87,12 @@ class CertificateStatuses(object): ...@@ -88,6 +87,12 @@ class CertificateStatuses(object):
unavailable = 'unavailable' unavailable = 'unavailable'
auditing = 'auditing' auditing = 'auditing'
readable_statuses = {
downloadable: "already received",
notpassing: "didn't receive",
error: "error states"
}
class CertificateSocialNetworks(object): class CertificateSocialNetworks(object):
""" """
...@@ -138,15 +143,26 @@ class CertificateWhitelist(models.Model): ...@@ -138,15 +143,26 @@ class CertificateWhitelist(models.Model):
if student: if student:
white_list = white_list.filter(user=student) white_list = white_list.filter(user=student)
result = [] result = []
generated_certificates = GeneratedCertificate.objects.filter(
course_id=course_id,
user__in=[exception.user for exception in white_list],
status=CertificateStatuses.downloadable
)
generated_certificates = {
certificate['user']: certificate['created_date']
for certificate in generated_certificates.values('user', 'created_date')
}
for item in white_list: for item in white_list:
certificate_generated = generated_certificates.get(item.user.id, '')
result.append({ result.append({
'id': item.id, 'id': item.id,
'user_id': item.user.id, 'user_id': item.user.id,
'user_name': unicode(item.user.username), 'user_name': unicode(item.user.username),
'user_email': unicode(item.user.email), 'user_email': unicode(item.user.email),
'course_id': unicode(item.course_id), 'course_id': unicode(item.course_id),
'created': item.created.strftime("%A, %B %d, %Y"), 'created': item.created.strftime("%B %d, %Y"),
'certificate_generated': certificate_generated and certificate_generated.strftime("%B %d, %Y"),
'notes': unicode(item.notes or ''), 'notes': unicode(item.notes or ''),
}) })
return result return result
...@@ -248,6 +264,40 @@ class CertificateGenerationHistory(TimeStampedModel): ...@@ -248,6 +264,40 @@ class CertificateGenerationHistory(TimeStampedModel):
instructor_task = models.ForeignKey(InstructorTask) instructor_task = models.ForeignKey(InstructorTask)
is_regeneration = models.BooleanField(default=False) is_regeneration = models.BooleanField(default=False)
def get_task_name(self):
"""
Return "regenerated" if record corresponds to Certificate Regeneration task, otherwise returns 'generated'
"""
return "regenerated" if self.is_regeneration else "generated"
def get_certificate_generation_candidates(self):
"""
Return the candidates for certificate generation task. It could either be students or certificate statuses
depending upon the nature of certificate generation task. Returned value could be one of the following,
1. "All learners" Certificate Generation task was initiated for all learners of the given course.
2. Comma separated list of certificate statuses, This usually happens when instructor regenerates certificates.
3. "for exceptions", This is the case when instructor generates certificates for white-listed
students.
"""
task_input = self.instructor_task.task_input
try:
task_input_json = json.loads(task_input)
except ValueError:
# if task input is empty, it means certificates were generated for all learners
return "All learners"
# get statuses_to_regenerate from task_input convert statuses to human readable strings and return
statuses = task_input_json.get('statuses_to_regenerate', None)
if statuses:
return ", ".join(
[CertificateStatuses.readable_statuses.get(status, "") for status in statuses]
)
# If statuses_to_regenerate is not present in task_input then, certificate generation task was run to
# generate certificates for white listed students
return "for exceptions"
class Meta(object): class Meta(object):
app_label = "certificates" app_label = "certificates"
......
...@@ -37,7 +37,13 @@ from student.models import CourseEnrollment ...@@ -37,7 +37,13 @@ from student.models import CourseEnrollment
from shoppingcart.models import Coupon, PaidCourseRegistration, CourseRegCodeItem from shoppingcart.models import Coupon, PaidCourseRegistration, CourseRegCodeItem
from course_modes.models import CourseMode, CourseModesArchive from course_modes.models import CourseMode, CourseModesArchive
from student.roles import CourseFinanceAdminRole, CourseSalesAdminRole from student.roles import CourseFinanceAdminRole, CourseSalesAdminRole
from certificates.models import CertificateGenerationConfiguration, CertificateWhitelist, GeneratedCertificate from certificates.models import (
CertificateGenerationConfiguration,
CertificateWhitelist,
GeneratedCertificate,
CertificateStatuses,
CertificateGenerationHistory,
)
from certificates import api as certs_api from certificates import api as certs_api
from util.date_utils import get_default_time_display from util.date_utils import get_default_time_display
...@@ -300,6 +306,10 @@ def _section_certificates(course): ...@@ -300,6 +306,10 @@ def _section_certificates(course):
) )
) )
instructor_generation_enabled = settings.FEATURES.get('CERTIFICATES_INSTRUCTOR_GENERATION', False) instructor_generation_enabled = settings.FEATURES.get('CERTIFICATES_INSTRUCTOR_GENERATION', False)
certificate_statuses_with_count = {
certificate['status']: certificate['count']
for certificate in GeneratedCertificate.get_unique_statuses(course_key=course.id)
}
return { return {
'section_key': 'certificates', 'section_key': 'certificates',
...@@ -310,7 +320,9 @@ def _section_certificates(course): ...@@ -310,7 +320,9 @@ def _section_certificates(course):
'instructor_generation_enabled': instructor_generation_enabled, 'instructor_generation_enabled': instructor_generation_enabled,
'html_cert_enabled': html_cert_enabled, 'html_cert_enabled': html_cert_enabled,
'active_certificate': certs_api.get_active_web_certificate(course), 'active_certificate': certs_api.get_active_web_certificate(course),
'certificate_statuses': GeneratedCertificate.get_unique_statuses(course_key=course.id), 'certificate_statuses_with_count': certificate_statuses_with_count,
'status': CertificateStatuses,
'certificate_generation_history': CertificateGenerationHistory.objects.filter(course_id=course.id),
'urls': { 'urls': {
'generate_example_certificates': reverse( 'generate_example_certificates': reverse(
'generate_example_certificates', 'generate_example_certificates',
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
user_name: '', user_name: '',
user_email: '', user_email: '',
created: '', created: '',
certificate_generated: '',
notes: '' notes: ''
}, },
......
...@@ -200,7 +200,7 @@ ...@@ -200,7 +200,7 @@
if (event && event.preventDefault) { event.preventDefault(); } if (event && event.preventDefault) { event.preventDefault(); }
if (event.currentTarget.files.length === 1) { if (event.currentTarget.files.length === 1) {
this.$el.find(DOM_SELECTORS.upload_csv_button).removeClass('is-disabled') this.$el.find(DOM_SELECTORS.upload_csv_button).removeClass('is-disabled')
.addClass('button-blue'); .addClass('btn-blue');
this.$el.find(DOM_SELECTORS.browse_file).val( this.$el.find(DOM_SELECTORS.browse_file).val(
event.currentTarget.value.substring(event.currentTarget.value.lastIndexOf("\\") + 1)); event.currentTarget.value.substring(event.currentTarget.value.lastIndexOf("\\") + 1));
} }
......
...@@ -81,23 +81,20 @@ var onCertificatesReady = null; ...@@ -81,23 +81,20 @@ var onCertificatesReady = null;
success: function (data) { success: function (data) {
$btn_regenerating_certs.attr('disabled','disabled'); $btn_regenerating_certs.attr('disabled','disabled');
if(data.success){ if(data.success){
$certificate_regeneration_status.text(data.message). $certificate_regeneration_status.text(data.message).addClass("message");
removeClass('msg-error').addClass('msg-success');
} }
else{ else{
$certificate_regeneration_status.text(data.message). $certificate_regeneration_status.text(data.message).addClass("message");
removeClass('msg-success').addClass("msg-error");
} }
}, },
error: function(jqXHR) { error: function(jqXHR) {
try{ try{
var response = JSON.parse(jqXHR.responseText); var response = JSON.parse(jqXHR.responseText);
$certificate_regeneration_status.text(gettext(response.message)). $certificate_regeneration_status.text(gettext(response.message)).addClass("message");
removeClass('msg-success').addClass("msg-error");
}catch(error){ }catch(error){
$certificate_regeneration_status. $certificate_regeneration_status.
text(gettext('Error while regenerating certificates. Please try again.')). text(gettext('Error while regenerating certificates. Please try again.')).
removeClass('msg-success').addClass("msg-error"); addClass("message");
} }
} }
}); });
......
...@@ -98,7 +98,7 @@ define([ ...@@ -98,7 +98,7 @@ define([
{ {
id: 1, user_id: 1, user_name: 'test1', user_email: 'test1@test.com', id: 1, user_id: 1, user_name: 'test1', user_email: 'test1@test.com',
course_id: 'edX/test/course', created: "Thursday, October 29, 2015", course_id: 'edX/test/course', created: "Thursday, October 29, 2015",
notes: 'test notes for test certificate exception' notes: 'test notes for test certificate exception', certificate_generated: ''
} }
); );
...@@ -106,7 +106,7 @@ define([ ...@@ -106,7 +106,7 @@ define([
{ {
id: 2, user_id: 2, user_name: 'test2', user_email: 'test2@test.com', id: 2, user_id: 2, user_name: 'test2', user_email: 'test2@test.com',
course_id: 'edX/test/course', created: "Thursday, October 29, 2015", course_id: 'edX/test/course', created: "Thursday, October 29, 2015",
notes: 'test notes for test certificate exception' notes: 'test notes for test certificate exception', certificate_generated: ''
} }
); );
}); });
...@@ -142,6 +142,7 @@ define([ ...@@ -142,6 +142,7 @@ define([
user_email: "", user_email: "",
created: "", created: "",
notes: "test3 notes", notes: "test3 notes",
certificate_generated : '',
new: true} new: true}
] ]
}; };
......
...@@ -17,8 +17,6 @@ define([ ...@@ -17,8 +17,6 @@ define([
server_error_message: "Error while regenerating certificates. Please try again." server_error_message: "Error while regenerating certificates. Please try again."
}; };
var expected = { var expected = {
error_class: 'msg-error',
success_class: 'msg-success',
url: 'test/url/', url: 'test/url/',
postData : [], postData : [],
selected_statuses: ['downloadable', 'error'], selected_statuses: ['downloadable', 'error'],
...@@ -27,29 +25,37 @@ define([ ...@@ -27,29 +25,37 @@ define([
var select_options = function(option_values){ var select_options = function(option_values){
$.each(option_values, function(index, element){ $.each(option_values, function(index, element){
$("#certificate-statuses option[value=" + element + "]").attr('selected', 'selected'); $("#certificate-regenerating-form input[value=" + element + "]").click();
}); });
}; };
beforeEach(function() { beforeEach(function() {
var fixture = '<section id = "certificates"><h2>Regenerate Certificates</h2>' + var fixture = '<section id="certificates">' +
'<form id="certificate-regenerating-form" method="post" action="' + expected.url + '">' + '<form id="certificate-regenerating-form" method="post" action="' + expected.url + '">' +
' <p id="status-multi-select-tip">Select one or more certificate statuses ' + '<p class="under-heading">To regenerate certificates for your course, ' +
' below using your mouse and ctrl or command key.</p>' + ' chose the learners who will receive regenerated certificates and click <br> ' +
' <select class="multi-select" multiple id="certificate-statuses" ' + ' Regenerate Certificates.' +
' name="certificate_statuses" aria-describedby="status-multi-select-tip">' + '</p>' +
' <option value="downloadable">Downloadable (2)</option>' + '<input id="certificate_status_downloadable" type="checkbox" name="certificate_statuses" ' +
' <option value="error">Error (2)</option>' + ' value="downloadable">' +
' <option value="generating">Generating (1)</option>' + '<label style="display: inline" for="certificate_status_downloadable">' +
' </select>' + ' Regenerate for learners who have already received certificates. (3)' +
' <label for="certificate-statuses">' + '</label><br>' +
' Select certificate statuses that need regeneration and click Regenerate ' + '<input id="certificate_status_notpassing" type="checkbox" name="certificate_statuses" ' +
' Certificates button.' + ' value="notpassing">' +
' </label>' + '<label style="display: inline" for="certificate_status_notpassing"> ' +
' <input type="button" id="btn-start-regenerating-certificates" value="Regenerate Certificates"' + ' Regenerate for learners who have not received certificates. (1)' +
' data-endpoint="' + expected.url + '"/>' + '</label><br>' +
'<input id="certificate_status_error" type="checkbox" name="certificate_statuses" ' +
' value="error">' +
'<label style="display: inline" for="certificate_status_error"> ' +
' Regenerate for learners in an error state. (0)' +
'</label><br>' +
'<input type="button" class="btn-blue" id="btn-start-regenerating-certificates" ' +
' value="Regenerate Certificates" data-endpoint="' + expected.url + '">' +
'</form>' + '</form>' +
'<div class="message certificate-regeneration-status"></div></section>'; '<div class="message certificate-regeneration-status"></div>' +
'</section>';
setFixtures(fixture); setFixtures(fixture);
onCertificatesReady(); onCertificatesReady();
...@@ -87,7 +93,6 @@ define([ ...@@ -87,7 +93,6 @@ define([
$regenerate_certificates_button.click(); $regenerate_certificates_button.click();
AjaxHelpers.respondWithError(requests, 500, {message: MESSAGES.server_error_message}); AjaxHelpers.respondWithError(requests, 500, {message: MESSAGES.server_error_message});
expect($certificate_regeneration_status).toHaveClass(expected.error_class);
expect($certificate_regeneration_status.text()).toEqual(MESSAGES.server_error_message); expect($certificate_regeneration_status.text()).toEqual(MESSAGES.server_error_message);
}); });
...@@ -97,7 +102,6 @@ define([ ...@@ -97,7 +102,6 @@ define([
$regenerate_certificates_button.click(); $regenerate_certificates_button.click();
AjaxHelpers.respondWithError(requests, 400, {message: MESSAGES.error_message}); AjaxHelpers.respondWithError(requests, 400, {message: MESSAGES.error_message});
expect($certificate_regeneration_status).toHaveClass(expected.error_class);
expect($certificate_regeneration_status.text()).toEqual(MESSAGES.error_message); expect($certificate_regeneration_status.text()).toEqual(MESSAGES.error_message);
}); });
...@@ -107,7 +111,6 @@ define([ ...@@ -107,7 +111,6 @@ define([
$regenerate_certificates_button.click(); $regenerate_certificates_button.click();
AjaxHelpers.respondWithJson(requests, {message: MESSAGES.success_message, success: true}); AjaxHelpers.respondWithJson(requests, {message: MESSAGES.success_message, success: true});
expect($certificate_regeneration_status).toHaveClass(expected.success_class);
expect($certificate_regeneration_status.text()).toEqual(MESSAGES.success_message); expect($certificate_regeneration_status.text()).toEqual(MESSAGES.success_message);
}); });
......
...@@ -2105,7 +2105,7 @@ input[name="subject"] { ...@@ -2105,7 +2105,7 @@ input[name="subject"] {
// -------------------- // --------------------
.instructor-dashboard-wrapper-2 section.idash-section#certificates { .instructor-dashboard-wrapper-2 section.idash-section#certificates {
%btn-blue { .btn-blue {
@extend %btn-primary-blue; @extend %btn-primary-blue;
padding: ($baseline/2.5) ($baseline/2); padding: ($baseline/2.5) ($baseline/2);
text-shadow: none; text-shadow: none;
...@@ -2118,6 +2118,46 @@ input[name="subject"] { ...@@ -2118,6 +2118,46 @@ input[name="subject"] {
border-top-style: groove; border-top-style: groove;
color: $black; color: $black;
} }
.certificates-wrapper{
.message{
@extend %exception-message;
}
}
p.under-heading {
margin: 12px 0 12px 0;
line-height: 23px;
}
hr.section-divider{
margin: 25px 0;
border-top: 7px solid #646464;
}
.certificate-generation-history{
table{
thead{
tr{
td.task-name{
width: 150px;
}
td.task-date{
width: 200px;
}
}
}
tbody{
tr{
td{
padding: 5px;
vertical-align: middle;
text-align: left;;
}
}
}
}
}
#certificate-white-list-editor { #certificate-white-list-editor {
.certificate-exception-inputs { .certificate-exception-inputs {
...@@ -2134,10 +2174,6 @@ input[name="subject"] { ...@@ -2134,10 +2174,6 @@ input[name="subject"] {
.message { .message {
@extend %exception-message; @extend %exception-message;
} }
.button-blue {
@extend %btn-blue;
}
} }
} }
...@@ -2155,16 +2191,15 @@ input[name="subject"] { ...@@ -2155,16 +2191,15 @@ input[name="subject"] {
text-align: left; text-align: left;
color: $gray; color: $gray;
&.date, &.email { &.date{
width: 230px; width: 150px;
}
&.user-id {
width: 60px;
} }
&.user-name { &.user-name {
width: 150px; width: 120px;
}
&.user-email {
width: 200px;
} }
&.action { &.action {
...@@ -2211,10 +2246,6 @@ input[name="subject"] { ...@@ -2211,10 +2246,6 @@ input[name="subject"] {
} }
} }
.button-blue {
@extend %btn-blue;
}
.message { .message {
@extend %exception-message; @extend %exception-message;
} }
...@@ -2225,10 +2256,6 @@ input[name="subject"] { ...@@ -2225,10 +2256,6 @@ input[name="subject"] {
border-bottom: 1px groove black; border-bottom: 1px groove black;
display: inline-block; display: inline-block;
} }
p.under-heading {
margin: 12px 0 12px 0;
line-height: 23px;
}
} }
.bulk-white-list-exception { .bulk-white-list-exception {
...@@ -2250,10 +2277,6 @@ input[name="subject"] { ...@@ -2250,10 +2277,6 @@ input[name="subject"] {
.arrow { .arrow {
font-weight: bold; font-weight: bold;
} }
.button-blue {
@extend %btn-blue;
}
} }
} }
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<textarea class='notes-field' id="notes" rows="10" placeholder="Free text notes" aria-describedby='notes-field-tip'></textarea> <textarea class='notes-field' id="notes" rows="10" placeholder="Free text notes" aria-describedby='notes-field-tip'></textarea>
</div> </div>
<div> <div>
<button type="button" class="button-blue" id="add-exception" ><%= gettext("Add to Exception List") %> </button> <button type="button" class="btn-blue" id="add-exception" ><%= gettext("Add to Exception List") %> </button>
</div> </div>
<div class='message hidden'></div> <div class='message hidden'></div>
</div> </div>
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<span id='generate-exception-certificates-radio-all-tip'><%- gettext('Generate a Certificate for all users on the Exception list') %></span> <span id='generate-exception-certificates-radio-all-tip'><%- gettext('Generate a Certificate for all users on the Exception list') %></span>
</label> </label>
</p> </p>
<button id="generate-exception-certificates" class="button-blue" type="button"><%= gettext('Generate Exception Certificates') %></button> <button id="generate-exception-certificates" class="btn-blue" type="button"><%= gettext('Generate Exception Certificates') %></button>
<br/> <br/>
<% if (certificates.length === 0) { %> <% if (certificates.length === 0) { %>
<p><%- gettext("No results") %></p> <p><%- gettext("No results") %></p>
...@@ -18,9 +18,9 @@ ...@@ -18,9 +18,9 @@
<table> <table>
<thead> <thead>
<th class='user-name'><%- gettext("Name") %></th> <th class='user-name'><%- gettext("Name") %></th>
<th class='user-id'><%- gettext("User ID") %></th>
<th class='user-email'><%- gettext("User Email") %></th> <th class='user-email'><%- gettext("User Email") %></th>
<th class='date'><%- gettext("Date Exception Granted") %></th> <th class='date'><%- gettext("Exception Granted") %></th>
<th class='date'><%- gettext("Certificate Generated") %></th>
<th class='notes'><%- gettext("Notes") %></th> <th class='notes'><%- gettext("Notes") %></th>
<th class='action'><%- gettext("Action") %></th> <th class='action'><%- gettext("Action") %></th>
</thead> </thead>
...@@ -30,9 +30,9 @@ ...@@ -30,9 +30,9 @@
%> %>
<tr> <tr>
<td><%- cert.get("user_name") %></td> <td><%- cert.get("user_name") %></td>
<td><%- cert.get("user_id") %></td>
<td><%- cert.get("user_email") %></td> <td><%- cert.get("user_email") %></td>
<td><%- cert.get("created") %></td> <td><%- cert.get("created") %></td>
<td><%- cert.get("certificate_generated") %></td>
<td><%- cert.get("notes") %></td> <td><%- cert.get("notes") %></td>
<td><button class='delete-exception' data-user_id='<%- cert.get("user_id") %>'><%- gettext("Remove from List") %></button></td> <td><button class='delete-exception' data-user_id='<%- cert.get("user_id") %>'><%- gettext("Remove from List") %></button></td>
</tr> </tr>
......
...@@ -20,7 +20,7 @@ import json ...@@ -20,7 +20,7 @@ import json
<form id="generate-example-certificates-form" method="post" action="${section_data['urls']['generate_example_certificates']}"> <form id="generate-example-certificates-form" method="post" action="${section_data['urls']['generate_example_certificates']}">
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}"> <input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}">
<input type="submit" id="generate-example-certificates-submit" value="${_('Generate Example Certificates')}"/> <input type="submit" class="btn-blue" id="generate-example-certificates-submit" value="${_('Generate Example Certificates')}"/>
</form> </form>
</div> </div>
% endif % endif
...@@ -40,7 +40,7 @@ import json ...@@ -40,7 +40,7 @@ import json
% endif % endif
% endfor % endfor
</ul> </ul>
<button id="refresh-example-certificate-status">${_("Refresh Status")}</button> <button class="btn-blue" id="refresh-example-certificate-status">${_("Refresh Status")}</button>
</div> </div>
% endif % endif
</div> </div>
...@@ -53,13 +53,13 @@ import json ...@@ -53,13 +53,13 @@ import json
<form id="enable-certificates-form" method="post" action="${section_data['urls']['enable_certificate_generation']}"> <form id="enable-certificates-form" method="post" action="${section_data['urls']['enable_certificate_generation']}">
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}"> <input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}">
<input type="hidden" id="certificates-enabled" name="certificates-enabled" value="false" /> <input type="hidden" id="certificates-enabled" name="certificates-enabled" value="false" />
<input type="submit" id="disable-certificates-submit" value="${_('Disable Student-Generated Certificates')}"/> <input type="submit" class="btn-blue" id="disable-certificates-submit" value="${_('Disable Student-Generated Certificates')}"/>
</form> </form>
% elif section_data['can_enable_for_course']: % elif section_data['can_enable_for_course']:
<form id="enable-certificates-form" method="post" action="${section_data['urls']['enable_certificate_generation']}"> <form id="enable-certificates-form" method="post" action="${section_data['urls']['enable_certificate_generation']}">
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}"> <input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}">
<input type="hidden" id="certificates-enabled" name="certificates-enabled" value="true" /> <input type="hidden" id="certificates-enabled" name="certificates-enabled" value="true" />
<input type="submit" id="enable-certificates-submit" value="${_('Enable Student-Generated Certificates')}"/> <input type="submit" class="btn-blue" id="enable-certificates-submit" value="${_('Enable Student-Generated Certificates')}"/>
</form> </form>
% else: % else:
<p>${_("You must successfully generate example certificates before you enable student-generated certificates.")}</p> <p>${_("You must successfully generate example certificates before you enable student-generated certificates.")}</p>
...@@ -68,7 +68,7 @@ import json ...@@ -68,7 +68,7 @@ import json
</div> </div>
% if section_data['instructor_generation_enabled'] and not (section_data['enabled_for_course'] and section_data['html_cert_enabled']): % if section_data['instructor_generation_enabled'] and not (section_data['enabled_for_course'] and section_data['html_cert_enabled']):
<hr /> <hr class="section-divider" />
<div class="start-certificate-generation"> <div class="start-certificate-generation">
<h2>${_("Generate Certificates")}</h2> <h2>${_("Generate Certificates")}</h2>
...@@ -77,7 +77,10 @@ import json ...@@ -77,7 +77,10 @@ import json
<p>${_("Course certificate generation requires an activated web certificate configuration.")}</p> <p>${_("Course certificate generation requires an activated web certificate configuration.")}</p>
<input type="button" id="disabled-btn-start-generating-certificates" class="is-disabled" aria-disabled="true" value="${_('Generate Certificates')}"/> <input type="button" id="disabled-btn-start-generating-certificates" class="is-disabled" aria-disabled="true" value="${_('Generate Certificates')}"/>
% else: % else:
<input type="button" id="btn-start-generating-certificates" value="${_('Generate Certificates')}" data-endpoint="${section_data['urls']['start_certificate_generation']}"/> <p class="under-heading">
${_("When you are ready to generate certificates for your course, click Generate Certificates. You do not need to do this<br/>if you have set the certificate mode to on-demand generation.")}
</p>
<input type="button" class="btn-blue" id="btn-start-generating-certificates" value="${_('Generate Certificates')}" data-endpoint="${section_data['urls']['start_certificate_generation']}"/>
%endif %endif
</form> </form>
<div class="certificate-generation-status"></div> <div class="certificate-generation-status"></div>
...@@ -101,22 +104,58 @@ import json ...@@ -101,22 +104,58 @@ import json
<p class="start-certificate-regeneration"> <p class="start-certificate-regeneration">
<h2>${_("Regenerate Certificates")}</h2> <h2>${_("Regenerate Certificates")}</h2>
<form id="certificate-regenerating-form" method="post" action="${section_data['urls']['start_certificate_regeneration']}"> <form id="certificate-regenerating-form" method="post" action="${section_data['urls']['start_certificate_regeneration']}">
<p id='status-multi-select-tip'>${_('Select one or more certificate statuses below using your mouse and ctrl or command key.')}</p> <p class="under-heading">
<select class="multi-select" multiple id="certificate-statuses" name="certificate_statuses" aria-describedby="status-multi-select-tip"> ${_('To regenerate certificates for your course, chose the learners who will receive regenerated certificates and click <br/> Regenerate Certificates.')}
%for status in section_data['certificate_statuses']: </p>
<option value="${status['status']}">${status['status'].title() + " ({})".format(status['count'])}</option>
%endfor <label style="display: inline" for="certificate_status_${section_data['status'].downloadable}">
</select> <input id="certificate_status_${section_data['status'].downloadable}" type="checkbox" name="certificate_statuses" value="${section_data['status'].downloadable}">
<label for="certificate-statuses"> ${_("Regenerate for learners who have already received certificates. ({count})").format(count=section_data['certificate_statuses_with_count'].get(section_data['status'].downloadable, 0))}
${_("Select certificate statuses that need regeneration and click Regenerate Certificates button.")} </label>
</label> <br/>
<label style="display: inline" for="certificate_status_${section_data['status'].notpassing}">
<input type="button" id="btn-start-regenerating-certificates" value="${_('Regenerate Certificates')}" data-endpoint="${section_data['urls']['start_certificate_regeneration']}"/> <input id="certificate_status_${section_data['status'].notpassing}" type="checkbox" name="certificate_statuses" value="${section_data['status'].notpassing}">
${_("Regenerate for learners who have not received certificates. ({count})").format(count=section_data['certificate_statuses_with_count'].get(section_data['status'].notpassing, 0))}
</label>
<br/>
<label style="display: inline" for="certificate_status_${section_data['status'].error}">
<input id="certificate_status_${section_data['status'].error}" type="checkbox" name="certificate_statuses" value="${section_data['status'].error}">
${_("Regenerate for learners in an error state. ({count})").format(count=section_data['certificate_statuses_with_count'].get(section_data['status'].error, 0))}
</label>
<br/>
<br/>
<input type="button" class="btn-blue" id="btn-start-regenerating-certificates" value="${_('Regenerate Certificates')}" data-endpoint="${section_data['urls']['start_certificate_regeneration']}"/>
</form> </form>
<div class="message certificate-regeneration-status"></div> <div class="certificate-regeneration-status"></div>
<hr>
<div class="certificate-generation-history">
<h2 class="title">${_("Certificate Generation History")}</h2>
<div class="certificate-generation-history-content">
<table>
<thead>
<tr>
<td class="task-name"></td>
<td class="task-date"></td>
<td class="task-details"></td>
</tr>
</thead>
<tbody>
% for history in section_data['certificate_generation_history']:
<tr>
<td>${history.get_task_name().title()}</td>
<td>${history.created.strftime("%B %d, %Y")}</td>
<td>${history.get_certificate_generation_candidates()}</td>
</tr>
% endfor
</tbody>
</table>
</div>
</div>
<div class="certificate-exception-container"> <div class="certificate-exception-container">
<hr> <hr class="section-divider">
<h2> ${_("SET CERTIFICATE EXCEPTIONS")} </h2> <h2> ${_("SET CERTIFICATE EXCEPTIONS")} </h2>
<p class="under-heading info"> <p class="under-heading info">
${_("Set exceptions to generate certificates for learners who did not qualify for a certificate but have " \ ${_("Set exceptions to generate certificates for learners who did not qualify for a certificate but have " \
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment