Skip to content

Commit

Permalink
FEC-24
Browse files Browse the repository at this point in the history
---

Add context to certificate webview.
Define permissions using bridgekeeper for views related to certificates.
Add support to instructor certificates generation.
If user has instructor permissions over a course, the staff permissions are also granted.
  • Loading branch information
Squirrel18 authored and mariajgrimaldi committed Oct 4, 2020
1 parent 83e8546 commit c6511ac
Show file tree
Hide file tree
Showing 11 changed files with 46 additions and 20 deletions.
3 changes: 2 additions & 1 deletion cms/djangoapps/contentstore/views/certificates.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
from course_modes.models import CourseMode
from edxmako.shortcuts import render_to_response
from student.auth import has_studio_write_access
from student.roles import GlobalStaff
from student.roles import GlobalStaff, CourseInstructorRole
from util.db import MYSQL_MAX_INT, generate_int_id
from util.json_request import JsonResponse
from xmodule.modulestore import EdxJSONEncoder
Expand Down Expand Up @@ -426,6 +426,7 @@ def certificates_list_handler(request, course_key_string):
'certificate_web_view_url': certificate_web_view_url,
'is_active': is_active,
'is_global_staff': GlobalStaff().has_user(request.user),
'is_course_instructor': CourseInstructorRole(course.id).has_user(request.user),
'certificate_activation_handler_url': activation_handler_url
})
elif "application/json" in request.META.get('HTTP_ACCEPT'):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ function(_, $, Course, CertificatePreview, TemplateHelpers, ViewHelpers, AjaxHel
num: 'course_num',
revision: 'course_rev'
});
window.CMS.User = {isGlobalStaff: true};
window.CMS.User = {isGlobalStaff: true, isCourseInstructor: true};

TemplateHelpers.installTemplate('certificate-web-preview', true);
appendSetFixtures('<div class="preview-certificate nav-actions"></div>');
Expand Down
1 change: 1 addition & 0 deletions cms/templates/certificates.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
CMS.User = CMS.User || {};
CMS.URL.UPLOAD_ASSET = '${upload_asset_url | n, js_escaped_string}';
CMS.User.isGlobalStaff = '${is_global_staff | n, js_escaped_string}'=='True' ? true : false;
CMS.User.isCourseInstructor = '${is_course_instructor | n, js_escaped_string}'=='True' ? true : false;
</script>
</%block>

Expand Down
6 changes: 4 additions & 2 deletions lms/djangoapps/certificates/tests/test_webview_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -969,9 +969,11 @@ def test_render_500_view_invalid_certificate_configuration(self):
response = self.client.get(test_url + "?preview=honor")
self.assertContains(response, "Invalid Certificate Configuration")

# Verify that Exception is raised when certificate is not in the preview mode
with self.assertRaises(Exception):
# Verify that no Exception is raised when certificate is not in the preview mode
try:
self.client.get(test_url)
except Exception: # pylint: disable=broad-except
self.fail("No exception should be raised when rendering an invalid certificate without preview")

@override_settings(FEATURES=FEATURES_WITH_CERTS_DISABLED)
def test_request_certificate_without_passing(self):
Expand Down
15 changes: 14 additions & 1 deletion lms/djangoapps/certificates/views/webview.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ def _update_organization_context(context, course):
partner_short_name = course.display_organization if course.display_organization else course.org
organizations = organization_api.get_course_organizations(course_id=course.id)
if organizations:
#TODO Need to add support for multiple organizations, Currently we are interested in the first one.
# TODO Need to add support for multiple organizations, Currently we are interested in the first one.
organization = organizations[0]
partner_long_name = organization.get('name', partner_long_name)
partner_short_name = organization.get('short_name', partner_short_name)
Expand Down Expand Up @@ -465,6 +465,17 @@ def render_preview_certificate(request, course_id):
return render_html_view(request, six.text_type(course_id))


def _update_minimal_context(context):
"""
This is an eduNEXT function to make the regular certificate work out of the box
"""
context['company_privacy_url'] = ''
context['logo_src'] = ''
context['company_tos_url'] = ''
context['company_about_url'] = ''
context['logo_url'] = ''


def render_cert_by_uuid(request, certificate_uuid):
"""
This public view generates an HTML representation of the specified certificate
Expand Down Expand Up @@ -573,6 +584,8 @@ def render_html_view(request, course_id, certificate=None):
with translation.override(certificate_language):
context = {'user_language': user_language}

_update_minimal_context(context) # load the bare minimum

_update_context_with_basic_info(context, course_id, platform_name, configuration)

context['certificate_data'] = active_configuration
Expand Down
16 changes: 13 additions & 3 deletions lms/djangoapps/instructor/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
ENABLE_CERTIFICATE_GENERATION = 'instructor.enable_certificate_generation'
GENERATE_CERTIFICATE_EXCEPTIONS = 'instructor.generate_certificate_exceptions'
GENERATE_BULK_CERTIFICATE_EXCEPTIONS = 'instructor.generate_bulk_certificate_exceptions'
GENERATE_EXAMPLE_CERTIFICATES = 'instructor.generate_example_certificates'
START_CERTIFICATE_GENERATION = 'instructor.start_certificate_generation'
START_CERTIFICATE_REGENERATION = 'instructor.start_certificate_regeneration'
CERTIFICATE_EXCEPTION_VIEW = 'instructor.certificate_exception_view'
CERTIFICATE_INVALIDATION_VIEW = 'instructor.certificate_invalidation_view'
GIVE_STUDENT_EXTENSION = 'instructor.give_student_extension'
VIEW_ISSUED_CERTIFICATES = 'instructor.view_issued_certificates'
CAN_RESEARCH = 'instructor.research'
Expand All @@ -36,9 +41,14 @@
perms[EDIT_COURSE_ACCESS] = HasAccessRule('instructor')
perms[EDIT_FORUM_ROLES] = HasAccessRule('staff')
perms[EDIT_INVOICE_VALIDATION] = HasAccessRule('staff')
perms[ENABLE_CERTIFICATE_GENERATION] = is_staff
perms[GENERATE_CERTIFICATE_EXCEPTIONS] = is_staff
perms[GENERATE_BULK_CERTIFICATE_EXCEPTIONS] = is_staff
perms[ENABLE_CERTIFICATE_GENERATION] = HasAccessRule('instructor')
perms[GENERATE_CERTIFICATE_EXCEPTIONS] = HasAccessRule('instructor')
perms[GENERATE_BULK_CERTIFICATE_EXCEPTIONS] = HasAccessRule('instructor')
perms[GENERATE_EXAMPLE_CERTIFICATES] = HasAccessRule('instructor')
perms[START_CERTIFICATE_GENERATION] = HasAccessRule('instructor')
perms[START_CERTIFICATE_REGENERATION] = HasAccessRule('instructor')
perms[CERTIFICATE_EXCEPTION_VIEW] = HasAccessRule('instructor')
perms[CERTIFICATE_INVALIDATION_VIEW] = HasAccessRule('instructor')
perms[GIVE_STUDENT_EXTENSION] = HasAccessRule('staff')
perms[VIEW_ISSUED_CERTIFICATES] = HasAccessRule('staff') | HasRolesRule('data_researcher')
# only global staff or those with the data_researcher role can access the data download tab
Expand Down
10 changes: 5 additions & 5 deletions lms/djangoapps/instructor/tests/test_certificates.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ def setUp(self):
CertificateGenerationConfiguration.objects.create(enabled=True)

def test_visible_only_to_global_staff(self):
# Instructors don't see the certificates section
# Instructors see the certificates section
self.client.login(username=self.instructor.username, password="test")
self._assert_certificates_visible(False)
self._assert_certificates_visible(True)

# Global staff can see the certificates section
self.client.login(username=self.global_staff.username, password="test")
Expand Down Expand Up @@ -238,10 +238,10 @@ def setUp(self):
def test_allow_only_global_staff(self, url_name):
url = reverse(url_name, kwargs={'course_id': self.course.id})

# Instructors do not have access
# Instructors have access
self.client.login(username=self.instructor.username, password='test')
response = self.client.post(url)
self.assertEqual(response.status_code, 403)
self.assertEqual(response.status_code, 302)

# Global staff have access
self.client.login(username=self.global_staff.username, password='test')
Expand Down Expand Up @@ -308,7 +308,7 @@ def test_certificate_generation_api_without_global_staff(self):

self.client.login(username=self.instructor.username, password='test')
response = self.client.post(url)
self.assertEqual(response.status_code, 403)
self.assertEqual(response.status_code, 200)

def test_certificate_generation_api_with_global_staff(self):
"""
Expand Down
10 changes: 5 additions & 5 deletions lms/djangoapps/instructor/views/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2963,7 +2963,7 @@ def _instructor_dash_url(course_key, section=None):
return url


@require_global_staff
@require_course_permission(permissions.GENERATE_EXAMPLE_CERTIFICATES)
@require_POST
def generate_example_certificates(request, course_id=None):
"""Start generating a set of example certificates.
Expand Down Expand Up @@ -3025,7 +3025,7 @@ def mark_student_can_skip_entrance_exam(request, course_id):
@transaction.non_atomic_requests
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_global_staff
@require_course_permission(permissions.START_CERTIFICATE_GENERATION)
@require_POST
@common_exceptions_400
def start_certificate_generation(request, course_id):
Expand All @@ -3047,7 +3047,7 @@ def start_certificate_generation(request, course_id):
@transaction.non_atomic_requests
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_global_staff
@require_course_permission(permissions.START_CERTIFICATE_REGENERATION)
@require_POST
@common_exceptions_400
def start_certificate_regeneration(request, course_id):
Expand Down Expand Up @@ -3089,7 +3089,7 @@ def start_certificate_regeneration(request, course_id):
@transaction.non_atomic_requests
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_global_staff
@require_course_permission(permissions.CERTIFICATE_EXCEPTION_VIEW)
@require_http_methods(['POST', 'DELETE'])
def certificate_exception_view(request, course_id):
"""
Expand Down Expand Up @@ -3401,7 +3401,7 @@ def build_row_errors(key, _user, row_count):
@transaction.non_atomic_requests
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_global_staff
@require_course_permission(permissions.CERTIFICATE_INVALIDATION_VIEW)
@require_http_methods(['POST', 'DELETE'])
def certificate_invalidation_view(request, course_id):
"""
Expand Down
2 changes: 1 addition & 1 deletion lms/djangoapps/instructor/views/instructor_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ def instructor_dashboard_2(request, course_id):
# and enable self-generated certificates for a course.
# Note: This is hidden for all CCXs
certs_enabled = CertificateGenerationConfiguration.current().enabled and not hasattr(course_key, 'ccx')
if certs_enabled and access['admin']:
if certs_enabled and access['instructor']:
sections.append(_section_certificates(course))

openassessment_blocks = modulestore().get_items(
Expand Down
Binary file added lms/static/certificates/images/ico-audit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion lms/static/certificates/sass/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,6 @@
}

// hide the fancy
.accomplishment-signatories .signatory-signature,
.accomplishment-type-symbol {
display: none;
}
Expand Down

0 comments on commit c6511ac

Please sign in to comment.