Merge pull request #10901 from edx/saleem-latif/SOL-1418
saleem-latif/SOL-1418: Revised Generate Certificates Section and added Certificate Generation UI
This commit is contained in:
@@ -673,7 +673,6 @@ class CertificatesTest(BaseInstructorDashboardTest):
|
||||
self.certificates_section.add_certificate_exception(self.user_name, notes)
|
||||
self.assertIn(self.user_name, 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
|
||||
# Revisit Page
|
||||
@@ -685,7 +684,6 @@ class CertificatesTest(BaseInstructorDashboardTest):
|
||||
# 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(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):
|
||||
"""
|
||||
@@ -701,13 +699,11 @@ class CertificatesTest(BaseInstructorDashboardTest):
|
||||
self.certificates_section.add_certificate_exception(self.user_name, notes)
|
||||
self.assertIn(self.user_name, 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
|
||||
self.certificates_section.remove_first_certificate_exception()
|
||||
self.assertNotIn(self.user_name, 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
|
||||
# Revisit Page
|
||||
@@ -719,7 +715,6 @@ class CertificatesTest(BaseInstructorDashboardTest):
|
||||
# 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(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):
|
||||
"""
|
||||
|
||||
@@ -45,7 +45,6 @@ Eligibility:
|
||||
then the student will be issued a certificate regardless of his grade,
|
||||
unless he has allow_certificate set to False.
|
||||
"""
|
||||
from datetime import datetime
|
||||
import json
|
||||
import logging
|
||||
import uuid
|
||||
@@ -88,6 +87,12 @@ class CertificateStatuses(object):
|
||||
unavailable = 'unavailable'
|
||||
auditing = 'auditing'
|
||||
|
||||
readable_statuses = {
|
||||
downloadable: "already received",
|
||||
notpassing: "didn't receive",
|
||||
error: "error states"
|
||||
}
|
||||
|
||||
|
||||
class CertificateSocialNetworks(object):
|
||||
"""
|
||||
@@ -138,15 +143,26 @@ class CertificateWhitelist(models.Model):
|
||||
if student:
|
||||
white_list = white_list.filter(user=student)
|
||||
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:
|
||||
certificate_generated = generated_certificates.get(item.user.id, '')
|
||||
result.append({
|
||||
'id': item.id,
|
||||
'user_id': item.user.id,
|
||||
'user_name': unicode(item.user.username),
|
||||
'user_email': unicode(item.user.email),
|
||||
'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 ''),
|
||||
})
|
||||
return result
|
||||
@@ -248,6 +264,40 @@ class CertificateGenerationHistory(TimeStampedModel):
|
||||
instructor_task = models.ForeignKey(InstructorTask)
|
||||
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):
|
||||
app_label = "certificates"
|
||||
|
||||
|
||||
@@ -37,7 +37,13 @@ from student.models import CourseEnrollment
|
||||
from shoppingcart.models import Coupon, PaidCourseRegistration, CourseRegCodeItem
|
||||
from course_modes.models import CourseMode, CourseModesArchive
|
||||
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 util.date_utils import get_default_time_display
|
||||
|
||||
@@ -300,6 +306,10 @@ def _section_certificates(course):
|
||||
)
|
||||
)
|
||||
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 {
|
||||
'section_key': 'certificates',
|
||||
@@ -310,7 +320,9 @@ def _section_certificates(course):
|
||||
'instructor_generation_enabled': instructor_generation_enabled,
|
||||
'html_cert_enabled': html_cert_enabled,
|
||||
'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': {
|
||||
'generate_example_certificates': reverse(
|
||||
'generate_example_certificates',
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
user_name: '',
|
||||
user_email: '',
|
||||
created: '',
|
||||
certificate_generated: '',
|
||||
notes: ''
|
||||
},
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@
|
||||
if (event && event.preventDefault) { event.preventDefault(); }
|
||||
if (event.currentTarget.files.length === 1) {
|
||||
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(
|
||||
event.currentTarget.value.substring(event.currentTarget.value.lastIndexOf("\\") + 1));
|
||||
}
|
||||
|
||||
@@ -81,23 +81,20 @@ var onCertificatesReady = null;
|
||||
success: function (data) {
|
||||
$btn_regenerating_certs.attr('disabled','disabled');
|
||||
if(data.success){
|
||||
$certificate_regeneration_status.text(data.message).
|
||||
removeClass('msg-error').addClass('msg-success');
|
||||
$certificate_regeneration_status.text(data.message).addClass("message");
|
||||
}
|
||||
else{
|
||||
$certificate_regeneration_status.text(data.message).
|
||||
removeClass('msg-success').addClass("msg-error");
|
||||
$certificate_regeneration_status.text(data.message).addClass("message");
|
||||
}
|
||||
},
|
||||
error: function(jqXHR) {
|
||||
try{
|
||||
var response = JSON.parse(jqXHR.responseText);
|
||||
$certificate_regeneration_status.text(gettext(response.message)).
|
||||
removeClass('msg-success').addClass("msg-error");
|
||||
$certificate_regeneration_status.text(gettext(response.message)).addClass("message");
|
||||
}catch(error){
|
||||
$certificate_regeneration_status.
|
||||
text(gettext('Error while regenerating certificates. Please try again.')).
|
||||
removeClass('msg-success').addClass("msg-error");
|
||||
addClass("message");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -98,7 +98,7 @@ define([
|
||||
{
|
||||
id: 1, user_id: 1, user_name: 'test1', user_email: 'test1@test.com',
|
||||
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([
|
||||
{
|
||||
id: 2, user_id: 2, user_name: 'test2', user_email: 'test2@test.com',
|
||||
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([
|
||||
user_email: "",
|
||||
created: "",
|
||||
notes: "test3 notes",
|
||||
certificate_generated : '',
|
||||
new: true}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -17,8 +17,6 @@ define([
|
||||
server_error_message: "Error while regenerating certificates. Please try again."
|
||||
};
|
||||
var expected = {
|
||||
error_class: 'msg-error',
|
||||
success_class: 'msg-success',
|
||||
url: 'test/url/',
|
||||
postData : [],
|
||||
selected_statuses: ['downloadable', 'error'],
|
||||
@@ -27,29 +25,37 @@ define([
|
||||
|
||||
var select_options = function(option_values){
|
||||
$.each(option_values, function(index, element){
|
||||
$("#certificate-statuses option[value=" + element + "]").attr('selected', 'selected');
|
||||
$("#certificate-regenerating-form input[value=" + element + "]").click();
|
||||
});
|
||||
};
|
||||
|
||||
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 + '">' +
|
||||
' <p id="status-multi-select-tip">Select one or more certificate statuses ' +
|
||||
' below using your mouse and ctrl or command key.</p>' +
|
||||
' <select class="multi-select" multiple id="certificate-statuses" ' +
|
||||
' name="certificate_statuses" aria-describedby="status-multi-select-tip">' +
|
||||
' <option value="downloadable">Downloadable (2)</option>' +
|
||||
' <option value="error">Error (2)</option>' +
|
||||
' <option value="generating">Generating (1)</option>' +
|
||||
' </select>' +
|
||||
' <label for="certificate-statuses">' +
|
||||
' Select certificate statuses that need regeneration and click Regenerate ' +
|
||||
' Certificates button.' +
|
||||
' </label>' +
|
||||
' <input type="button" id="btn-start-regenerating-certificates" value="Regenerate Certificates"' +
|
||||
' data-endpoint="' + expected.url + '"/>' +
|
||||
'<p class="under-heading">To regenerate certificates for your course, ' +
|
||||
' chose the learners who will receive regenerated certificates and click <br> ' +
|
||||
' Regenerate Certificates.' +
|
||||
'</p>' +
|
||||
'<input id="certificate_status_downloadable" type="checkbox" name="certificate_statuses" ' +
|
||||
' value="downloadable">' +
|
||||
'<label style="display: inline" for="certificate_status_downloadable">' +
|
||||
' Regenerate for learners who have already received certificates. (3)' +
|
||||
'</label><br>' +
|
||||
'<input id="certificate_status_notpassing" type="checkbox" name="certificate_statuses" ' +
|
||||
' value="notpassing">' +
|
||||
'<label style="display: inline" for="certificate_status_notpassing"> ' +
|
||||
' Regenerate for learners who have not received certificates. (1)' +
|
||||
'</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>' +
|
||||
'<div class="message certificate-regeneration-status"></div></section>';
|
||||
'<div class="message certificate-regeneration-status"></div>' +
|
||||
'</section>';
|
||||
|
||||
setFixtures(fixture);
|
||||
onCertificatesReady();
|
||||
@@ -87,7 +93,6 @@ define([
|
||||
|
||||
$regenerate_certificates_button.click();
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -97,7 +102,6 @@ define([
|
||||
|
||||
$regenerate_certificates_button.click();
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -107,7 +111,6 @@ define([
|
||||
|
||||
$regenerate_certificates_button.click();
|
||||
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);
|
||||
});
|
||||
|
||||
|
||||
@@ -2105,7 +2105,7 @@ input[name="subject"] {
|
||||
// --------------------
|
||||
.instructor-dashboard-wrapper-2 section.idash-section#certificates {
|
||||
|
||||
%btn-blue {
|
||||
.btn-blue {
|
||||
@extend %btn-primary-blue;
|
||||
padding: ($baseline/2.5) ($baseline/2);
|
||||
text-shadow: none;
|
||||
@@ -2118,6 +2118,46 @@ input[name="subject"] {
|
||||
border-top-style: groove;
|
||||
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-exception-inputs {
|
||||
@@ -2134,10 +2174,6 @@ input[name="subject"] {
|
||||
.message {
|
||||
@extend %exception-message;
|
||||
}
|
||||
|
||||
.button-blue {
|
||||
@extend %btn-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2155,16 +2191,15 @@ input[name="subject"] {
|
||||
text-align: left;
|
||||
color: $gray;
|
||||
|
||||
&.date, &.email {
|
||||
width: 230px;
|
||||
}
|
||||
|
||||
&.user-id {
|
||||
width: 60px;
|
||||
&.date{
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
&.user-name {
|
||||
width: 150px;
|
||||
width: 120px;
|
||||
}
|
||||
&.user-email {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
&.action {
|
||||
@@ -2211,10 +2246,6 @@ input[name="subject"] {
|
||||
}
|
||||
}
|
||||
|
||||
.button-blue {
|
||||
@extend %btn-blue;
|
||||
}
|
||||
|
||||
.message {
|
||||
@extend %exception-message;
|
||||
}
|
||||
@@ -2225,10 +2256,6 @@ input[name="subject"] {
|
||||
border-bottom: 1px groove black;
|
||||
display: inline-block;
|
||||
}
|
||||
p.under-heading {
|
||||
margin: 12px 0 12px 0;
|
||||
line-height: 23px;
|
||||
}
|
||||
}
|
||||
|
||||
.bulk-white-list-exception {
|
||||
@@ -2250,10 +2277,6 @@ input[name="subject"] {
|
||||
.arrow {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.button-blue {
|
||||
@extend %btn-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<textarea class='notes-field' id="notes" rows="10" placeholder="Free text notes" aria-describedby='notes-field-tip'></textarea>
|
||||
</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 class='message hidden'></div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<span id='generate-exception-certificates-radio-all-tip'><%- gettext('Generate a Certificate for all users on the Exception list') %></span>
|
||||
</label>
|
||||
</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/>
|
||||
<% if (certificates.length === 0) { %>
|
||||
<p><%- gettext("No results") %></p>
|
||||
@@ -18,9 +18,9 @@
|
||||
<table>
|
||||
<thead>
|
||||
<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='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='action'><%- gettext("Action") %></th>
|
||||
</thead>
|
||||
@@ -30,9 +30,9 @@
|
||||
%>
|
||||
<tr>
|
||||
<td><%- cert.get("user_name") %></td>
|
||||
<td><%- cert.get("user_id") %></td>
|
||||
<td><%- cert.get("user_email") %></td>
|
||||
<td><%- cert.get("created") %></td>
|
||||
<td><%- cert.get("certificate_generated") %></td>
|
||||
<td><%- cert.get("notes") %></td>
|
||||
<td><button class='delete-exception' data-user_id='<%- cert.get("user_id") %>'><%- gettext("Remove from List") %></button></td>
|
||||
</tr>
|
||||
|
||||
@@ -20,7 +20,7 @@ import json
|
||||
|
||||
<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="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>
|
||||
</div>
|
||||
% endif
|
||||
@@ -40,7 +40,7 @@ import json
|
||||
% endif
|
||||
% endfor
|
||||
</ul>
|
||||
<button id="refresh-example-certificate-status">${_("Refresh Status")}</button>
|
||||
<button class="btn-blue" id="refresh-example-certificate-status">${_("Refresh Status")}</button>
|
||||
</div>
|
||||
% endif
|
||||
</div>
|
||||
@@ -53,13 +53,13 @@ import json
|
||||
<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" 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>
|
||||
% elif section_data['can_enable_for_course']:
|
||||
<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" 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>
|
||||
% else:
|
||||
<p>${_("You must successfully generate example certificates before you enable student-generated certificates.")}</p>
|
||||
@@ -68,7 +68,7 @@ import json
|
||||
</div>
|
||||
|
||||
% 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">
|
||||
<h2>${_("Generate Certificates")}</h2>
|
||||
@@ -77,7 +77,10 @@ import json
|
||||
<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')}"/>
|
||||
% 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
|
||||
</form>
|
||||
<div class="certificate-generation-status"></div>
|
||||
@@ -101,22 +104,58 @@ import json
|
||||
<p class="start-certificate-regeneration">
|
||||
<h2>${_("Regenerate Certificates")}</h2>
|
||||
<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>
|
||||
<select class="multi-select" multiple id="certificate-statuses" name="certificate_statuses" aria-describedby="status-multi-select-tip">
|
||||
%for status in section_data['certificate_statuses']:
|
||||
<option value="${status['status']}">${status['status'].title() + " ({})".format(status['count'])}</option>
|
||||
%endfor
|
||||
</select>
|
||||
<label for="certificate-statuses">
|
||||
${_("Select certificate statuses that need regeneration and click Regenerate Certificates button.")}
|
||||
</label>
|
||||
<p class="under-heading">
|
||||
${_('To regenerate certificates for your course, chose the learners who will receive regenerated certificates and click <br/> Regenerate Certificates.')}
|
||||
</p>
|
||||
|
||||
<input type="button" id="btn-start-regenerating-certificates" value="${_('Regenerate Certificates')}" data-endpoint="${section_data['urls']['start_certificate_regeneration']}"/>
|
||||
<label style="display: inline" for="certificate_status_${section_data['status'].downloadable}">
|
||||
<input id="certificate_status_${section_data['status'].downloadable}" type="checkbox" name="certificate_statuses" value="${section_data['status'].downloadable}">
|
||||
${_("Regenerate for learners who have already received certificates. ({count})").format(count=section_data['certificate_statuses_with_count'].get(section_data['status'].downloadable, 0))}
|
||||
</label>
|
||||
<br/>
|
||||
<label style="display: inline" for="certificate_status_${section_data['status'].notpassing}">
|
||||
<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>
|
||||
<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">
|
||||
<hr>
|
||||
<hr class="section-divider">
|
||||
<h2> ${_("SET CERTIFICATE EXCEPTIONS")} </h2>
|
||||
<p class="under-heading info">
|
||||
${_("Set exceptions to generate certificates for learners who did not qualify for a certificate but have " \
|
||||
|
||||
Reference in New Issue
Block a user