Merge pull request #7195 from edx/will/certs-instructor-dash
ECOM-1140: Instructor dashboard example certificates
This commit is contained in:
@@ -15,6 +15,7 @@ from django.http import Http404, HttpResponseBadRequest
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.test.utils import override_settings
|
||||
from certificates import api as certs_api
|
||||
from certificates.models import CertificateStatuses, CertificateGenerationConfiguration
|
||||
from certificates.tests.factories import GeneratedCertificateFactory
|
||||
from edxmako.middleware import MakoMiddleware
|
||||
@@ -677,10 +678,19 @@ class ProgressPageTests(ModuleStoreTestCase):
|
||||
resp = views.progress(self.request, course_id=self.course.id.to_deprecated_string())
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
def test_resp_with_generate_cert_config_enabled(self):
|
||||
def test_generate_cert_config(self):
|
||||
resp = views.progress(self.request, course_id=unicode(self.course.id))
|
||||
self.assertNotContains(resp, 'Create Your Certificate')
|
||||
|
||||
# Enable the feature, but do not enable it for this course
|
||||
CertificateGenerationConfiguration(enabled=True).save()
|
||||
resp = views.progress(self.request, course_id=unicode(self.course.id))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertNotContains(resp, 'Create Your Certificate')
|
||||
|
||||
# Enable certificate generation for this course
|
||||
certs_api.set_cert_generation_enabled(self.course.id, True)
|
||||
resp = views.progress(self.request, course_id=unicode(self.course.id))
|
||||
self.assertContains(resp, 'Create Your Certificate')
|
||||
|
||||
|
||||
class VerifyCourseKeyDecoratorTests(TestCase):
|
||||
|
||||
@@ -23,8 +23,7 @@ from django.utils.timezone import UTC
|
||||
from django.views.decorators.http import require_GET, require_POST
|
||||
from django.http import Http404, HttpResponse, HttpResponseBadRequest
|
||||
from django.shortcuts import redirect
|
||||
from certificates.api import certificate_downloadable_status, generate_user_certificates
|
||||
from certificates.models import CertificateGenerationConfiguration
|
||||
from certificates import api as certs_api
|
||||
from edxmako.shortcuts import render_to_response, render_to_string, marketing_link
|
||||
from django_future.csrf import ensure_csrf_cookie
|
||||
from django.views.decorators.cache import cache_control
|
||||
@@ -1013,7 +1012,7 @@ def _progress(request, course_key, student_id):
|
||||
raise Http404
|
||||
|
||||
# checking certificate generation configuration
|
||||
show_generate_cert_btn = CertificateGenerationConfiguration.current().enabled
|
||||
show_generate_cert_btn = certs_api.cert_generation_enabled(course_key)
|
||||
|
||||
context = {
|
||||
'course': course,
|
||||
@@ -1023,12 +1022,12 @@ def _progress(request, course_key, student_id):
|
||||
'staff_access': staff_access,
|
||||
'student': student,
|
||||
'reverifications': fetch_reverify_banner_info(request, course_key),
|
||||
'passed': is_course_passed(course, grade_summary) if show_generate_cert_btn else False,
|
||||
'passed': is_course_passed(course, grade_summary),
|
||||
'show_generate_cert_btn': show_generate_cert_btn
|
||||
}
|
||||
|
||||
if show_generate_cert_btn:
|
||||
context.update(certificate_downloadable_status(student, course_key))
|
||||
context.update(certs_api.certificate_downloadable_status(student, course_key))
|
||||
|
||||
with grades.manual_transaction():
|
||||
response = render_to_response('courseware/progress.html', context)
|
||||
@@ -1301,10 +1300,10 @@ def generate_user_cert(request, course_id):
|
||||
if not is_course_passed(course, None, student, request):
|
||||
return HttpResponseBadRequest(_("Your certificate will be available when you pass the course."))
|
||||
|
||||
certificate_status = certificate_downloadable_status(student, course.id)
|
||||
certificate_status = certs_api.certificate_downloadable_status(student, course.id)
|
||||
|
||||
if not certificate_status["is_downloadable"] and not certificate_status["is_generating"]:
|
||||
generate_user_certificates(student, course.id, course=course)
|
||||
certs_api.generate_user_certificates(student, course.id)
|
||||
_track_successful_certificate_generation(student.id, course.id)
|
||||
return HttpResponse(_("Creating certificate"))
|
||||
|
||||
|
||||
221
lms/djangoapps/instructor/tests/test_certificates.py
Normal file
221
lms/djangoapps/instructor/tests/test_certificates.py
Normal file
@@ -0,0 +1,221 @@
|
||||
"""Tests for the certificates panel of the instructor dash. """
|
||||
import contextlib
|
||||
import ddt
|
||||
import mock
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test.utils import override_settings
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from config_models.models import cache
|
||||
from courseware.tests.factories import GlobalStaffFactory, InstructorFactory
|
||||
from certificates.models import CertificateGenerationConfiguration
|
||||
from certificates import api as certs_api
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class CertificatesInstructorDashTest(ModuleStoreTestCase):
|
||||
"""Tests for the certificate panel of the instructor dash. """
|
||||
|
||||
ERROR_REASON = "An error occurred!"
|
||||
DOWNLOAD_URL = "http://www.example.com/abcd123/cert.pdf"
|
||||
|
||||
def setUp(self):
|
||||
super(CertificatesInstructorDashTest, self).setUp()
|
||||
self.course = CourseFactory.create()
|
||||
self.url = reverse(
|
||||
'instructor_dashboard',
|
||||
kwargs={'course_id': unicode(self.course.id)}
|
||||
)
|
||||
self.global_staff = GlobalStaffFactory()
|
||||
self.instructor = InstructorFactory(course_key=self.course.id)
|
||||
|
||||
# Need to clear the cache for model-based configuration
|
||||
cache.clear()
|
||||
|
||||
# Enable the certificate generation feature
|
||||
CertificateGenerationConfiguration.objects.create(enabled=True)
|
||||
|
||||
def test_visible_only_to_global_staff(self):
|
||||
# Instructors don't see the certificates section
|
||||
self.client.login(username=self.instructor.username, password="test")
|
||||
self._assert_certificates_visible(False)
|
||||
|
||||
# Global staff can see the certificates section
|
||||
self.client.login(username=self.global_staff.username, password="test")
|
||||
self._assert_certificates_visible(True)
|
||||
|
||||
def test_visible_only_when_feature_flag_enabled(self):
|
||||
# Disable the feature flag
|
||||
CertificateGenerationConfiguration.objects.create(enabled=False)
|
||||
cache.clear()
|
||||
|
||||
# Now even global staff can't see the certificates section
|
||||
self.client.login(username=self.global_staff.username, password="test")
|
||||
self._assert_certificates_visible(False)
|
||||
|
||||
@ddt.data("started", "error", "success")
|
||||
def test_show_certificate_status(self, status):
|
||||
self.client.login(username=self.global_staff.username, password="test")
|
||||
with self._certificate_status("honor", status):
|
||||
self._assert_certificate_status("honor", status)
|
||||
|
||||
def test_show_enabled_button(self):
|
||||
self.client.login(username=self.global_staff.username, password="test")
|
||||
|
||||
# Initially, no example certs are generated, so
|
||||
# the enable button should be disabled
|
||||
self._assert_enable_certs_button_is_disabled()
|
||||
|
||||
with self._certificate_status("honor", "success"):
|
||||
# Certs are disabled for the course, so the enable button should be shown
|
||||
self._assert_enable_certs_button(True)
|
||||
|
||||
# Enable certificates for the course
|
||||
certs_api.set_cert_generation_enabled(self.course.id, True)
|
||||
|
||||
# Now the "disable" button should be shown
|
||||
self._assert_enable_certs_button(False)
|
||||
|
||||
def test_can_disable_even_after_failure(self):
|
||||
self.client.login(username=self.global_staff.username, password="test")
|
||||
|
||||
with self._certificate_status("honor", "error"):
|
||||
# When certs are disabled for a course, then don't allow them
|
||||
# to be enabled if certificate generation doesn't complete successfully
|
||||
certs_api.set_cert_generation_enabled(self.course.id, False)
|
||||
self._assert_enable_certs_button_is_disabled()
|
||||
|
||||
# However, if certificates are already enabled, allow them
|
||||
# to be disabled even if an error has occurred
|
||||
certs_api.set_cert_generation_enabled(self.course.id, True)
|
||||
self._assert_enable_certs_button(False)
|
||||
|
||||
def _assert_certificates_visible(self, is_visible):
|
||||
"""Check that the certificates section is visible on the instructor dash. """
|
||||
response = self.client.get(self.url)
|
||||
if is_visible:
|
||||
self.assertContains(response, "Certificates")
|
||||
else:
|
||||
self.assertNotContains(response, "Certificates")
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _certificate_status(self, description, status):
|
||||
"""Configure the certificate status by mocking the certificates API. """
|
||||
patched = 'instructor.views.instructor_dashboard.certs_api.example_certificates_status'
|
||||
with mock.patch(patched) as certs_api_status:
|
||||
cert_status = [{
|
||||
'description': description,
|
||||
'status': status
|
||||
}]
|
||||
|
||||
if status == 'error':
|
||||
cert_status[0]['error_reason'] = self.ERROR_REASON
|
||||
if status == 'success':
|
||||
cert_status[0]['download_url'] = self.DOWNLOAD_URL
|
||||
|
||||
certs_api_status.return_value = cert_status
|
||||
yield
|
||||
|
||||
def _assert_certificate_status(self, cert_name, expected_status):
|
||||
"""Check the certificate status display on the instructor dash. """
|
||||
response = self.client.get(self.url)
|
||||
|
||||
if expected_status == 'started':
|
||||
expected = 'Generating example {name} certificate'.format(name=cert_name)
|
||||
self.assertContains(response, expected)
|
||||
elif expected_status == 'error':
|
||||
expected = self.ERROR_REASON
|
||||
self.assertContains(response, expected)
|
||||
elif expected_status == 'success':
|
||||
expected = self.DOWNLOAD_URL
|
||||
self.assertContains(response, expected)
|
||||
else:
|
||||
self.fail("Invalid certificate status: {status}".format(status=expected_status))
|
||||
|
||||
def _assert_enable_certs_button_is_disabled(self):
|
||||
"""Check that the "enable student-generated certificates" button is disabled. """
|
||||
response = self.client.get(self.url)
|
||||
expected_html = '<button class="is-disabled" disabled>Enable Student-Generated Certificates</button>'
|
||||
self.assertContains(response, expected_html)
|
||||
|
||||
def _assert_enable_certs_button(self, is_enabled):
|
||||
"""Check whether the button says "enable" or "disable" cert generation. """
|
||||
response = self.client.get(self.url)
|
||||
expected_html = (
|
||||
'Enable Student-Generated Certificates' if is_enabled
|
||||
else 'Disable Student-Generated Certificates'
|
||||
)
|
||||
self.assertContains(response, expected_html)
|
||||
|
||||
|
||||
@override_settings(CERT_QUEUE='certificates')
|
||||
@ddt.ddt
|
||||
class CertificatesInstructorApiTest(ModuleStoreTestCase):
|
||||
"""Tests for the certificates end-points in the instructor dash API. """
|
||||
|
||||
def setUp(self):
|
||||
super(CertificatesInstructorApiTest, self).setUp()
|
||||
self.course = CourseFactory.create()
|
||||
self.global_staff = GlobalStaffFactory()
|
||||
self.instructor = InstructorFactory(course_key=self.course.id)
|
||||
|
||||
# Enable certificate generation
|
||||
cache.clear()
|
||||
CertificateGenerationConfiguration.objects.create(enabled=True)
|
||||
|
||||
@ddt.data('generate_example_certificates', 'enable_certificate_generation')
|
||||
def test_allow_only_global_staff(self, url_name):
|
||||
url = reverse(url_name, kwargs={'course_id': self.course.id})
|
||||
|
||||
# Instructors do not have access
|
||||
self.client.login(username=self.instructor.username, password='test')
|
||||
response = self.client.post(url)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
# Global staff have access
|
||||
self.client.login(username=self.global_staff.username, password='test')
|
||||
response = self.client.post(url)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_generate_example_certificates(self):
|
||||
self.client.login(username=self.global_staff.username, password='test')
|
||||
url = reverse(
|
||||
'generate_example_certificates',
|
||||
kwargs={'course_id': unicode(self.course.id)}
|
||||
)
|
||||
response = self.client.post(url)
|
||||
|
||||
# Expect a redirect back to the instructor dashboard
|
||||
self._assert_redirects_to_instructor_dash(response)
|
||||
|
||||
# Expect that certificate generation started
|
||||
# Cert generation will fail here because XQueue isn't configured,
|
||||
# but the status should at least not be None.
|
||||
status = certs_api.example_certificates_status(self.course.id)
|
||||
self.assertIsNot(status, None)
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_enable_certificate_generation(self, is_enabled):
|
||||
self.client.login(username=self.global_staff.username, password='test')
|
||||
url = reverse(
|
||||
'enable_certificate_generation',
|
||||
kwargs={'course_id': unicode(self.course.id)}
|
||||
)
|
||||
params = {'certificates-enabled': 'true' if is_enabled else 'false'}
|
||||
response = self.client.post(url, data=params)
|
||||
|
||||
# Expect a redirect back to the instructor dashboard
|
||||
self._assert_redirects_to_instructor_dash(response)
|
||||
|
||||
# Expect that certificate generation is now enabled for the course
|
||||
actual_enabled = certs_api.cert_generation_enabled(self.course.id)
|
||||
self.assertEqual(is_enabled, actual_enabled)
|
||||
|
||||
def _assert_redirects_to_instructor_dash(self, response):
|
||||
"""Check that the response redirects to the certificates section. """
|
||||
expected_redirect = reverse(
|
||||
'instructor_dashboard',
|
||||
kwargs={'course_id': unicode(self.course.id)}
|
||||
)
|
||||
expected_redirect += '#view-certificates'
|
||||
self.assertRedirects(response, expected_redirect)
|
||||
@@ -23,16 +23,15 @@ from django.core.validators import validate_email
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotFound
|
||||
from django.utils.html import strip_tags
|
||||
from django.shortcuts import redirect
|
||||
import string # pylint: disable=deprecated-module
|
||||
import random
|
||||
import unicodecsv
|
||||
import urllib
|
||||
import decimal
|
||||
from student import auth
|
||||
from student.roles import CourseSalesAdminRole
|
||||
from student.roles import GlobalStaff, CourseSalesAdminRole
|
||||
from util.file import store_uploaded_file, course_and_time_based_filename_generator, FileValidationException, UniversalNewlineIterator
|
||||
import datetime
|
||||
import pytz
|
||||
from util.json_request import JsonResponse
|
||||
from instructor.views.instructor_task_helpers import extract_email_features, extract_task_features
|
||||
|
||||
@@ -58,7 +57,10 @@ from shoppingcart.models import (
|
||||
CourseMode,
|
||||
CourseRegistrationCodeInvoiceItem,
|
||||
)
|
||||
from student.models import CourseEnrollment, unique_id_for_user, anonymous_id_for_user, EntranceExamConfiguration
|
||||
from student.models import (
|
||||
CourseEnrollment, unique_id_for_user, anonymous_id_for_user,
|
||||
UserProfile, Registration, EntranceExamConfiguration
|
||||
)
|
||||
import instructor_task.api
|
||||
from instructor_task.api_helper import AlreadyRunningError
|
||||
from instructor_task.models import ReportStore
|
||||
@@ -82,6 +84,8 @@ from instructor.views import INVOICE_KEY
|
||||
|
||||
from submissions import api as sub_api # installed from the edx-submissions repository
|
||||
|
||||
from certificates import api as certs_api
|
||||
|
||||
from bulk_email.models import CourseEmail
|
||||
|
||||
from .tools import (
|
||||
@@ -100,7 +104,6 @@ from .tools import (
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
from opaque_keys import InvalidKeyError
|
||||
from student.models import UserProfile, Registration
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -234,6 +237,20 @@ def require_level(level):
|
||||
return decorator
|
||||
|
||||
|
||||
def require_global_staff(func):
|
||||
"""View decorator that requires that the user have global staff permissions. """
|
||||
def wrapped(request, *args, **kwargs): # pylint: disable=missing-docstring
|
||||
if GlobalStaff().has_user(request.user):
|
||||
return func(request, *args, **kwargs)
|
||||
else:
|
||||
return HttpResponseForbidden(
|
||||
u"Must be {platform_name} staff to perform this action.".format(
|
||||
platform_name=settings.PLATFORM_NAME
|
||||
)
|
||||
)
|
||||
return wrapped
|
||||
|
||||
|
||||
def require_sales_admin(func):
|
||||
"""
|
||||
Decorator for checking sales administrator access before executing an HTTP endpoint. This decorator
|
||||
@@ -2284,6 +2301,60 @@ def _split_input_list(str_list):
|
||||
return new_list
|
||||
|
||||
|
||||
def _instructor_dash_url(course_key, section=None):
|
||||
"""Return the URL for a section in the instructor dashboard.
|
||||
|
||||
Arguments:
|
||||
course_key (CourseKey)
|
||||
|
||||
Keyword Arguments:
|
||||
section (str): The name of the section to load.
|
||||
|
||||
Returns:
|
||||
unicode: The URL of a section in the instructor dashboard.
|
||||
|
||||
"""
|
||||
url = reverse('instructor_dashboard', kwargs={'course_id': unicode(course_key)})
|
||||
if section is not None:
|
||||
url += u'#view-{section}'.format(section=section)
|
||||
return url
|
||||
|
||||
|
||||
@require_global_staff
|
||||
@require_POST
|
||||
def generate_example_certificates(request, course_id=None): # pylint: disable=unused-argument
|
||||
"""Start generating a set of example certificates.
|
||||
|
||||
Example certificates are used to verify that certificates have
|
||||
been configured correctly for the course.
|
||||
|
||||
Redirects back to the intructor dashboard once certificate
|
||||
generation has begun.
|
||||
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
certs_api.generate_example_certificates(course_key)
|
||||
return redirect(_instructor_dash_url(course_key, section='certificates'))
|
||||
|
||||
|
||||
@require_global_staff
|
||||
@require_POST
|
||||
def enable_certificate_generation(request, course_id=None):
|
||||
"""Enable/disable self-generated certificates for a course.
|
||||
|
||||
Once self-generated certificates have been enabled, students
|
||||
who have passed the course will be able to generate certificates.
|
||||
|
||||
Redirects back to the intructor dashboard once the
|
||||
setting has been updated.
|
||||
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
is_enabled = (request.POST.get('certificates-enabled', 'false') == 'true')
|
||||
certs_api.set_cert_generation_enabled(course_key, is_enabled)
|
||||
return redirect(_instructor_dash_url(course_key, section='certificates'))
|
||||
|
||||
|
||||
#---- Gradebook (shown to small courses only) ----
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
|
||||
@@ -109,4 +109,13 @@ urlpatterns = patterns(
|
||||
# Cohort management
|
||||
url(r'add_users_to_cohorts$',
|
||||
'instructor.views.api.add_users_to_cohorts', name="add_users_to_cohorts"),
|
||||
|
||||
# Certificates
|
||||
url(r'^generate_example_certificates$',
|
||||
'instructor.views.api.generate_example_certificates',
|
||||
name='generate_example_certificates'),
|
||||
|
||||
url(r'^enable_certificate_generation$',
|
||||
'instructor.views.api.enable_certificate_generation',
|
||||
name='enable_certificate_generation'),
|
||||
)
|
||||
|
||||
@@ -36,6 +36,8 @@ from student.models import CourseEnrollment
|
||||
from shoppingcart.models import Coupon, PaidCourseRegistration
|
||||
from course_modes.models import CourseMode, CourseModesArchive
|
||||
from student.roles import CourseFinanceAdminRole, CourseSalesAdminRole
|
||||
from certificates.models import CertificateGenerationConfiguration
|
||||
from certificates import api as certs_api
|
||||
|
||||
from class_dashboard.dashboard_data import get_section_display_name, get_array_section_has_problem
|
||||
from .tools import get_units_with_due_date, title_or_url, bulk_email_is_enabled_for_course
|
||||
@@ -108,6 +110,13 @@ def instructor_dashboard_2(request, course_id):
|
||||
if course_mode_has_price and (access['finance_admin'] or access['sales_admin']):
|
||||
sections.append(_section_e_commerce(course, access, paid_modes[0], is_white_label))
|
||||
|
||||
# Certificates panel
|
||||
# This is used to generate example certificates
|
||||
# and enable self-generated certificates for a course.
|
||||
certs_enabled = CertificateGenerationConfiguration.current().enabled
|
||||
if certs_enabled and access['admin']:
|
||||
sections.append(_section_certificates(course))
|
||||
|
||||
disable_buttons = not _is_small_course(course_key)
|
||||
|
||||
analytics_dashboard_message = None
|
||||
@@ -182,6 +191,53 @@ def _section_e_commerce(course, access, paid_mode, coupons_enabled):
|
||||
return section_data
|
||||
|
||||
|
||||
def _section_certificates(course):
|
||||
"""Section information for the certificates panel.
|
||||
|
||||
The certificates panel allows global staff to generate
|
||||
example certificates and enable self-generated certificates
|
||||
for a course.
|
||||
|
||||
Arguments:
|
||||
course (Course)
|
||||
|
||||
Returns:
|
||||
dict
|
||||
|
||||
"""
|
||||
example_cert_status = certs_api.example_certificates_status(course.id)
|
||||
|
||||
# Allow the user to enable self-generated certificates for students
|
||||
# *only* once a set of example certificates has been successfully generated.
|
||||
# If certificates have been misconfigured for the course (for example, if
|
||||
# the PDF template hasn't been uploaded yet), then we don't want
|
||||
# to turn on self-generated certificates for students!
|
||||
can_enable_for_course = (
|
||||
example_cert_status is not None and
|
||||
all(
|
||||
cert_status['status'] == 'success'
|
||||
for cert_status in example_cert_status
|
||||
)
|
||||
)
|
||||
return {
|
||||
'section_key': 'certificates',
|
||||
'section_display_name': _('Certificates'),
|
||||
'example_certificate_status': example_cert_status,
|
||||
'can_enable_for_course': can_enable_for_course,
|
||||
'enabled_for_course': certs_api.cert_generation_enabled(course.id),
|
||||
'urls': {
|
||||
'generate_example_certificates': reverse(
|
||||
'generate_example_certificates',
|
||||
kwargs={'course_id': course.id}
|
||||
),
|
||||
'enable_certificate_generation': reverse(
|
||||
'enable_certificate_generation',
|
||||
kwargs={'course_id': course.id}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_POST
|
||||
|
||||
@@ -1097,7 +1097,10 @@ rwd_header_footer_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/common_he
|
||||
staff_grading_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/staff_grading/**/*.js'))
|
||||
open_ended_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/open_ended/**/*.js'))
|
||||
notes_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/notes/**/*.js'))
|
||||
instructor_dash_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/instructor_dashboard/**/*.js'))
|
||||
instructor_dash_js = (
|
||||
sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/instructor_dashboard/**/*.js')) +
|
||||
sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/instructor_dashboard/**/*.js'))
|
||||
)
|
||||
|
||||
# JavaScript used by the student account and profile pages
|
||||
# These are not courseware, so they do not need many of the courseware-specific
|
||||
|
||||
37
lms/static/js/instructor_dashboard/certificates.js
Normal file
37
lms/static/js/instructor_dashboard/certificates.js
Normal file
@@ -0,0 +1,37 @@
|
||||
var edx = edx || {};
|
||||
|
||||
(function( $, gettext ) {
|
||||
'use strict';
|
||||
|
||||
edx.instructor_dashboard = edx.instructor_dashboard || {};
|
||||
edx.instructor_dashboard.certificates = {};
|
||||
|
||||
$(function() {
|
||||
/**
|
||||
* Show a confirmation message before letting staff members
|
||||
* enable/disable self-generated certificates for a course.
|
||||
*/
|
||||
$('#enable-certificates-form').on('submit', function( event ) {
|
||||
var isEnabled = $('#certificates-enabled').val() === 'true',
|
||||
confirmMessage = '';
|
||||
|
||||
if ( isEnabled ) {
|
||||
confirmMessage = gettext('Allow students to generate certificates for this course?');
|
||||
} else {
|
||||
confirmMessage = gettext('Prevent students from generating certificates in this course?');
|
||||
}
|
||||
|
||||
if ( !confirm( confirmMessage ) ) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Refresh the status for example certificate generation
|
||||
* by reloading the instructor dashboard.
|
||||
*/
|
||||
$('#refresh-example-certificate-status').on('click', function() {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
})( $, gettext );
|
||||
@@ -1773,6 +1773,17 @@ input[name="subject"] {
|
||||
}
|
||||
}
|
||||
|
||||
.certificates-wrapper {
|
||||
.generate-example-certificates-wrapper {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.example-certificate-status-wrapper {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.profile-distribution-widget {
|
||||
margin-bottom: ($baseline * 2);
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%page args="section_data"/>
|
||||
<div class="certificates-wrapper">
|
||||
|
||||
<div class="example-certificates">
|
||||
<h2>${_('Example Certificates')}</h2>
|
||||
|
||||
<div class="generate-example-certificates-wrapper">
|
||||
<p>${_('Generate example certificates for the course.')}</p>
|
||||
|
||||
<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')}"/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
% if section_data['example_certificate_status'] is not None:
|
||||
<div class="example-certificate-status-wrapper">
|
||||
<p>${_("Status:")}</p>
|
||||
<ul>
|
||||
% for cert_status in section_data['example_certificate_status']:
|
||||
% if cert_status['status'] == 'started':
|
||||
<li>${_('Generating example {name} certificate').format(name=cert_status['description'])}</li>
|
||||
% elif cert_status['status'] == 'error':
|
||||
<li>${_('Error generating example {name} certificate: {error}').format(name=cert_status['description'], error=cert_status['error_reason'])}</li>
|
||||
% elif cert_status['status'] == 'success':
|
||||
<li><a href="${cert_status['download_url']}">${_('View {name} certificate').format(name=cert_status['description'])}</a></li>
|
||||
</li>
|
||||
% endif
|
||||
% endfor
|
||||
</ul>
|
||||
<button id="refresh-example-certificate-status">${_("Refresh Status")}</button>
|
||||
</div>
|
||||
% endif
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="enable-certificates">
|
||||
<h2>${_("Student-Generated Certificates")}</h2>
|
||||
% if section_data['enabled_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="false" />
|
||||
<input type="submit" 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')}"/>
|
||||
</form>
|
||||
% else:
|
||||
<p>${_("You must successfully generate example certificates before you enable student-generated certificates.")}</p>
|
||||
<button class="is-disabled" disabled>${_('Enable Student-Generated Certificates')}</button>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
@@ -53,7 +53,6 @@
|
||||
<%static:js group='application'/>
|
||||
|
||||
## Backbone classes declared explicitly until RequireJS is supported
|
||||
<script type="text/javascript" src="${static.url('js/instructor_dashboard/ecommerce.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/models/notification.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/views/notification.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/views/file_uploader.js')}"></script>
|
||||
|
||||
Reference in New Issue
Block a user