Commit ab713181 by Alex Dusenbery Committed by Alex Dusenbery

EDUCATOR-1316 | Refactor courseware.views.views._get_cert_data and related functions.

parent fd07dea0
......@@ -376,7 +376,7 @@ def _cert_info(user, course_overview, cert_status, course_mode): # pylint: disa
if status == 'ready':
# showing the certificate web view button if certificate is ready state and feature flags are enabled.
if has_html_certificates_enabled(course_overview.id, course_overview):
if has_html_certificates_enabled(course_overview):
if course_overview.has_any_active_web_certificate:
status_dict.update({
'show_cert_web_view': True,
......
......@@ -150,7 +150,12 @@ def generate_user_certificates(student, course_key, course=None, insecure=False,
xqueue = XQueueCertInterface()
if insecure:
xqueue.use_https = False
generate_pdf = not has_html_certificates_enabled(course_key, course)
if not course:
course = modulestore().get_course(course_key, depth=0)
generate_pdf = not has_html_certificates_enabled(course)
cert = xqueue.add_cert(
student,
course_key,
......@@ -198,7 +203,11 @@ def regenerate_user_certificates(student, course_key, course=None,
if insecure:
xqueue.use_https = False
generate_pdf = not has_html_certificates_enabled(course_key, course)
if not course:
course = modulestore().get_course(course_key, depth=0)
generate_pdf = not has_html_certificates_enabled(course)
return xqueue.regen_cert(
student,
course_key,
......@@ -353,44 +362,6 @@ def generate_example_certificates(course_key):
xqueue.add_example_cert(cert)
def has_html_certificates_enabled(course_key, course=None):
"""
Determine if a course has html certificates enabled.
Arguments:
course_key (CourseKey|str): A course key or a string representation
of one.
course (CourseDescriptor|CourseOverview): A course.
"""
# If the feature is disabled, then immediately return a False
if not settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False):
return False
# If we don't have a course object, we'll need to assemble one
if not course:
# Initialize a course key if necessary
if not isinstance(course_key, CourseKey):
try:
course_key = CourseKey.from_string(course_key)
except InvalidKeyError:
log.warning(
('Unable to parse course_key "%s"', course_key),
exc_info=True
)
return False
# Pull the course data from the cache
try:
course = CourseOverview.get_from_id(course_key)
except: # pylint: disable=bare-except
log.warning(
('Unable to load CourseOverview object for course_key "%s"', unicode(course_key)),
exc_info=True
)
# Return the flag on the course object
return course.cert_html_view_enabled if course else False
def example_certificates_status(course_key):
"""Check the status of example certificates for a course.
......@@ -425,50 +396,58 @@ def example_certificates_status(course_key):
return ExampleCertificateSet.latest_status(course_key)
def _safe_course_key(course_key):
if not isinstance(course_key, CourseKey):
return CourseKey.from_string(course_key)
return course_key
def _course_from_key(course_key):
return CourseOverview.get_from_id(_safe_course_key(course_key))
def _certificate_html_url(user_id, course_id, uuid):
if uuid:
return reverse('certificates:render_cert_by_uuid', kwargs={'certificate_uuid': uuid})
elif user_id and course_id:
kwargs = {"user_id": str(user_id), "course_id": unicode(course_id)}
return reverse('certificates:html_view', kwargs=kwargs)
return ''
def _certificate_download_url(user_id, course_id):
try:
user_certificate = GeneratedCertificate.eligible_certificates.get(
user=user_id,
course_id=_safe_course_key(course_id)
)
return user_certificate.download_url
except GeneratedCertificate.DoesNotExist:
log.critical(
'Unable to lookup certificate\n'
'user id: %d\n'
'course: %s', user_id, unicode(course_id)
)
return ''
def has_html_certificates_enabled(course):
if not settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False):
return False
return course.cert_html_view_enabled
def get_certificate_url(user_id=None, course_id=None, uuid=None):
"""
:return certificate url for web or pdf certs. In case of web certs returns either old
or new cert url based on given parameters. For web certs if `uuid` is it would return
new uuid based cert url url otherwise old url.
"""
url = ""
if has_html_certificates_enabled(course_id):
if uuid:
url = reverse(
'certificates:render_cert_by_uuid',
kwargs=dict(certificate_uuid=uuid)
)
elif user_id and course_id:
url = reverse(
'certificates:html_view',
kwargs={
"user_id": str(user_id),
"course_id": unicode(course_id),
}
)
else:
if isinstance(course_id, basestring):
try:
course_id = CourseKey.from_string(course_id)
except InvalidKeyError:
log.warning(
('Unable to parse course_id "%s"', course_id),
exc_info=True
)
return url
try:
user_certificate = GeneratedCertificate.eligible_certificates.get(
user=user_id,
course_id=course_id
)
url = user_certificate.download_url
except GeneratedCertificate.DoesNotExist:
log.critical(
'Unable to lookup certificate\n'
'user id: %d\n'
'course: %s', user_id, unicode(course_id)
)
url = ''
course = _course_from_key(course_id)
if not course:
return url
if has_html_certificates_enabled(course):
url = _certificate_html_url(user_id, course_id, uuid)
else:
url = _certificate_download_url(user_id, course_id)
return url
......
......@@ -674,7 +674,7 @@ class CertificatesViewsTests(CommonCertificatesTestCase):
self._add_course_certificates(count=1, signatory_count=0)
test_url = get_certificate_url(
user_id=self.user.id,
course_id=unicode(self.course)
course_id=unicode(self.course.id)
)
response = self.client.get(test_url)
self.assertNotIn('Signatory_Name 0', response.content)
......
......@@ -26,8 +26,7 @@ from certificates.api import (
get_certificate_footer_context,
get_certificate_header_context,
get_certificate_template,
get_certificate_url,
has_html_certificates_enabled
get_certificate_url
)
from certificates.models import (
CertificateGenerationCourseSetting,
......@@ -48,8 +47,7 @@ from student.models import LinkedInAddToProfileConfiguration
from util import organizations_helpers as organization_api
from util.date_utils import strftime_localized
from util.views import handle_500
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
log = logging.getLogger(__name__)
......@@ -523,23 +521,18 @@ def render_html_view(request, user_id, course_id):
_update_context_with_basic_info(context, course_id, platform_name, configuration)
invalid_template_path = 'certificates/invalid.html'
# Kick the user back to the "Invalid" screen if the feature is disabled
if not has_html_certificates_enabled(course_id):
log.info(
"Invalid cert: HTML certificates disabled for %s. User id: %d",
course_id,
user_id,
)
# Kick the user back to the "Invalid" screen if the feature is disabled globally
if not settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False):
return render_to_response(invalid_template_path, context)
# Load the course and user objects
try:
course_key = CourseKey.from_string(course_id)
user = User.objects.get(id=user_id)
course = modulestore().get_course(course_key)
course = get_course_by_id(course_key)
# For any other expected exceptions, kick the user back to the "Invalid" screen
except (InvalidKeyError, ItemNotFoundError, User.DoesNotExist) as exception:
# For any course or user exceptions, kick the user back to the "Invalid" screen
except (InvalidKeyError, User.DoesNotExist, Http404) as exception:
error_str = (
"Invalid cert: error finding course %s or user with id "
"%d. Specific error: %s"
......@@ -547,6 +540,15 @@ def render_html_view(request, user_id, course_id):
log.info(error_str, course_id, user_id, str(exception))
return render_to_response(invalid_template_path, context)
# Kick the user back to the "Invalid" screen if the feature is disabled for the course
if not course.cert_html_view_enabled:
log.info(
"Invalid cert: HTML certificates disabled for %s. User id: %d",
course_id,
user_id,
)
return render_to_response(invalid_template_path, context)
# Load user's certificate
user_certificate = _get_user_certificate(request, user, course_key, course, preview_mode)
if not user_certificate:
......
......@@ -331,7 +331,7 @@ def _section_certificates(course):
"""
example_cert_status = None
html_cert_enabled = certs_api.has_html_certificates_enabled(course.id, course)
html_cert_enabled = certs_api.has_html_certificates_enabled(course)
if html_cert_enabled:
can_enable_for_course = True
else:
......
......@@ -1976,7 +1976,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
'failed': 3,
'skipped': 2
}
with self.assertNumQueries(171):
with self.assertNumQueries(176):
self.assertCertificatesGenerated(task_input, expected_results)
expected_results = {
......
......@@ -2,8 +2,12 @@
The public API for certificates.
"""
from datetime import datetime
from pytz import UTC
from course_modes.models import CourseMode
from openedx.core.djangoapps.certificates.config import waffle
from student.models import CourseEnrollment
SWITCHES = waffle.waffle()
......@@ -19,6 +23,48 @@ def _enabled_and_instructor_paced(course):
return False
def certificates_viewable_for_course(course):
"""
Returns True if certificates are viewable for any student enrolled in the course, False otherwise.
"""
if course.self_paced:
return True
if (
course.certificates_display_behavior in ('early_with_info', 'early_no_info')
or course.certificates_show_before_end
):
return True
if (
course.certificate_available_date
and course.certificate_available_date <= datetime.now(UTC)
):
return True
if (
course.certificate_available_date is None
and course.has_ended()
):
return True
return False
def is_certificate_valid(certificate):
"""
Returns True if the student has a valid, verified certificate for this course, False otherwise.
"""
return CourseEnrollment.is_enrolled_as_verified(certificate.user, certificate.course_id) and certificate.is_valid()
def can_show_certificate_message(course, student, course_grade, certificates_enabled_for_course):
if not (
(auto_certificate_generation_enabled() or certificates_enabled_for_course) and
CourseEnrollment.is_enrolled(student, course.id) and
certificates_viewable_for_course(course) and
course_grade.passed
):
return False
return True
def can_show_certificate_available_date_field(course):
return _enabled_and_instructor_paced(course)
......
"""
Openedx Certificates Application Configuration
"""
from django.apps import AppConfig
class OpenedxCertificatesConfig(AppConfig):
"""
Application Configuration for Openedx Certificates.
"""
name = 'openedx.core.djangoapps.certificates'
label = 'openedx_certificates'
from contextlib import contextmanager
from datetime import datetime, timedelta
import itertools
from unittest import TestCase
import ddt
import pytz
import waffle
from course_modes.models import CourseMode
from openedx.core.djangoapps.certificates import api
from openedx.core.djangoapps.certificates.config import waffle as certs_waffle
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
from student.tests.factories import CourseEnrollmentFactory, UserFactory
# TODO: Copied from lms.djangoapps.certificates.models,
# to be resolved per https://openedx.atlassian.net/browse/EDUCATOR-1318
class CertificateStatuses(object):
"""
Enum for certificate statuses
"""
deleted = 'deleted'
deleting = 'deleting'
downloadable = 'downloadable'
error = 'error'
generating = 'generating'
notpassing = 'notpassing'
restricted = 'restricted'
unavailable = 'unavailable'
auditing = 'auditing'
audit_passing = 'audit_passing'
audit_notpassing = 'audit_notpassing'
unverified = 'unverified'
invalidated = 'invalidated'
requesting = 'requesting'
ALL_STATUSES = (
deleted, deleting, downloadable, error, generating, notpassing, restricted, unavailable, auditing,
audit_passing, audit_notpassing, unverified, invalidated, requesting
)
class MockGeneratedCertificate(object):
"""
We can't import GeneratedCertificate from LMS here, so we roll
our own minimal Certificate model for testing.
"""
def __init__(self, user=None, course_id=None, mode=None, status=None):
self.user = user
self.course_id = course_id
self.mode = mode
self.status = status
def is_valid(self):
"""
Return True if certificate is valid else return False.
"""
return self.status == CertificateStatuses.downloadable
@contextmanager
......@@ -15,18 +64,29 @@ def configure_waffle_namespace(feature_enabled):
namespace = certs_waffle.waffle()
with namespace.override(certs_waffle.AUTO_CERTIFICATE_GENERATION, active=feature_enabled):
yield
yield
@ddt.ddt
class FeatureEnabledTestCase(TestCase):
class CertificatesApiTestCase(TestCase):
def setUp(self):
super(FeatureEnabledTestCase, self).setUp()
self.course = CourseOverviewFactory.create()
def tearDown(self):
super(FeatureEnabledTestCase, self).tearDown()
self.course.self_paced = False
super(CertificatesApiTestCase, self).setUp()
self.course = CourseOverviewFactory.create(
start=datetime(2017, 1, 1, tzinfo=pytz.UTC),
end=datetime(2017, 1, 31, tzinfo=pytz.UTC),
certificate_available_date=None
)
self.user = UserFactory.create()
self.enrollment = CourseEnrollmentFactory(
user=self.user,
course_id=self.course.id,
is_active=True,
mode='audit',
)
self.certificate = MockGeneratedCertificate(
user=self.user,
course_id=self.course.id
)
@ddt.data(True, False)
def test_auto_certificate_generation_enabled(self, feature_enabled):
......@@ -46,3 +106,18 @@ class FeatureEnabledTestCase(TestCase):
self.course.self_paced = is_self_paced
with configure_waffle_namespace(feature_enabled):
self.assertEqual(expected_value, api.can_show_certificate_available_date_field(self.course))
@ddt.data(
(CourseMode.VERIFIED, CertificateStatuses.downloadable, True),
(CourseMode.VERIFIED, CertificateStatuses.notpassing, False),
(CourseMode.AUDIT, CertificateStatuses.downloadable, False)
)
@ddt.unpack
def test_is_certificate_valid(self, enrollment_mode, certificate_status, expected_value):
self.enrollment.mode = enrollment_mode
self.enrollment.save()
self.certificate.mode = CourseMode.VERIFIED
self.certificate.status = certificate_status
self.assertEqual(expected_value, api.is_certificate_valid(self.certificate))
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