Commit ffd72c10 by Sofiya Semenova Committed by GitHub

Merge pull request #15705 from edx/ssemenova/ed-526

EDUCATOR-526 Update view certificate button
parents b8e48146 76f1df6b
...@@ -514,6 +514,7 @@ class DashboardTest(ModuleStoreTestCase): ...@@ -514,6 +514,7 @@ class DashboardTest(ModuleStoreTestCase):
expiration_datetime=datetime.now(pytz.UTC) - timedelta(days=1) expiration_datetime=datetime.now(pytz.UTC) - timedelta(days=1)
) )
self.course.certificate_available_date = datetime.now(pytz.UTC) - timedelta(days=1)
CourseEnrollment.enroll(self.user, self.course.id, mode='honor') CourseEnrollment.enroll(self.user, self.course.id, mode='honor')
self.course.start = datetime.now(pytz.UTC) - timedelta(days=2) self.course.start = datetime.now(pytz.UTC) - timedelta(days=2)
......
...@@ -93,7 +93,12 @@ def course_start_date_is_default(start, advertised_start): ...@@ -93,7 +93,12 @@ def course_start_date_is_default(start, advertised_start):
return advertised_start is None and start == DEFAULT_START_DATE return advertised_start is None and start == DEFAULT_START_DATE
def may_certify_for_course(certificates_display_behavior, certificates_show_before_end, has_ended): def may_certify_for_course(
certificates_display_behavior,
certificates_show_before_end,
has_ended,
certificate_available_date
):
""" """
Returns whether it is acceptable to show the student a certificate download Returns whether it is acceptable to show the student a certificate download
link for a course. link for a course.
...@@ -105,12 +110,24 @@ def may_certify_for_course(certificates_display_behavior, certificates_show_befo ...@@ -105,12 +110,24 @@ def may_certify_for_course(certificates_display_behavior, certificates_show_befo
certificates_show_before_end (bool): whether user can download the certificates_show_before_end (bool): whether user can download the
course's certificates before the course has ended. course's certificates before the course has ended.
has_ended (bool): Whether the course has ended. has_ended (bool): Whether the course has ended.
certificate_available_date (datetime): the date the certificate is available on for the course.
""" """
show_early = ( show_early = (
certificates_display_behavior in ('early_with_info', 'early_no_info') certificates_display_behavior in ('early_with_info', 'early_no_info')
or certificates_show_before_end or certificates_show_before_end
) )
return show_early or has_ended past_availability_date = (
certificate_available_date
and certificate_available_date < datetime.now(utc)
)
if show_early:
return True
if past_availability_date:
return True
if (certificate_available_date is None) and has_ended:
return True
return False
def sorting_score(start, advertised_start, announcement): def sorting_score(start, advertised_start, announcement):
......
...@@ -1065,7 +1065,8 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin): ...@@ -1065,7 +1065,8 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin):
return course_metadata_utils.may_certify_for_course( return course_metadata_utils.may_certify_for_course(
self.certificates_display_behavior, self.certificates_display_behavior,
self.certificates_show_before_end, self.certificates_show_before_end,
self.has_ended() self.has_ended(),
self.certificate_available_date
) )
def has_started(self): def has_started(self):
......
...@@ -161,11 +161,14 @@ class CourseMetadataUtilsTestCase(TestCase): ...@@ -161,11 +161,14 @@ class CourseMetadataUtilsTestCase(TestCase):
TestScenario((DEFAULT_START_DATE, None), True), TestScenario((DEFAULT_START_DATE, None), True),
]), ]),
FunctionTest(may_certify_for_course, [ FunctionTest(may_certify_for_course, [
TestScenario(('early_with_info', True, True), True), TestScenario(('early_with_info', True, True, test_datetime), True),
TestScenario(('early_no_info', False, False), True), TestScenario(('early_no_info', False, False, test_datetime), True),
TestScenario(('end', True, False), True), TestScenario(('end', True, False, test_datetime), True),
TestScenario(('end', False, True), True), TestScenario(('end', False, True, test_datetime), True),
TestScenario(('end', False, False), False), TestScenario(('end', False, False, _NEXT_WEEK), False),
TestScenario(('end', False, False, _LAST_WEEK), True),
TestScenario(('end', False, False, None), False),
TestScenario(('early_with_info', False, False, None), True),
]), ]),
] ]
......
...@@ -4,6 +4,7 @@ from contextlib import contextmanager ...@@ -4,6 +4,7 @@ from contextlib import contextmanager
from functools import wraps from functools import wraps
import ddt import ddt
from datetime import datetime
from config_models.models import cache from config_models.models import cache
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
...@@ -92,7 +93,8 @@ class CertificateDownloadableStatusTests(WebCertificateTestMixin, ModuleStoreTes ...@@ -92,7 +93,8 @@ class CertificateDownloadableStatusTests(WebCertificateTestMixin, ModuleStoreTes
self.course = CourseFactory.create( self.course = CourseFactory.create(
org='edx', org='edx',
number='verified', number='verified',
display_name='Verified Course' display_name='Verified Course',
end=datetime.now()
) )
self.request_factory = RequestFactory() self.request_factory = RequestFactory()
......
...@@ -1216,6 +1216,8 @@ class ProgressPageBaseTests(ModuleStoreTestCase): ...@@ -1216,6 +1216,8 @@ class ProgressPageBaseTests(ModuleStoreTestCase):
self.course = CourseFactory.create( self.course = CourseFactory.create(
start=datetime(2013, 9, 16, 7, 17, 28), start=datetime(2013, 9, 16, 7, 17, 28),
grade_cutoffs={u'çü†øƒƒ': 0.75, 'Pass': 0.5}, grade_cutoffs={u'çü†øƒƒ': 0.75, 'Pass': 0.5},
end=datetime.now(),
certificate_available_date=datetime.now(),
**options **options
) )
...@@ -2052,6 +2054,7 @@ class GenerateUserCertTests(ModuleStoreTestCase): ...@@ -2052,6 +2054,7 @@ class GenerateUserCertTests(ModuleStoreTestCase):
self.course = CourseFactory.create( self.course = CourseFactory.create(
org='edx', org='edx',
number='verified', number='verified',
end=datetime.now(),
display_name='Verified Course', display_name='Verified Course',
grade_cutoffs={'cutoff': 0.75, 'Pass': 0.5} grade_cutoffs={'cutoff': 0.75, 'Pass': 0.5}
) )
......
...@@ -112,7 +112,9 @@ log = logging.getLogger("edx.courseware") ...@@ -112,7 +112,9 @@ log = logging.getLogger("edx.courseware")
# credit and verified modes. # credit and verified modes.
REQUIREMENTS_DISPLAY_MODES = CourseMode.CREDIT_MODES + [CourseMode.VERIFIED] REQUIREMENTS_DISPLAY_MODES = CourseMode.CREDIT_MODES + [CourseMode.VERIFIED]
CertData = namedtuple("CertData", ["cert_status", "title", "msg", "download_url", "cert_web_view_url"]) CertData = namedtuple(
"CertData", ["cert_status", "title", "msg", "download_url", "cert_web_view_url", "may_view_certificate"]
)
def user_groups(user): def user_groups(user):
...@@ -919,6 +921,7 @@ def _get_cert_data(student, course, course_key, is_active, enrollment_mode): ...@@ -919,6 +921,7 @@ def _get_cert_data(student, course, course_key, is_active, enrollment_mode):
Returns: Returns:
returns dict if course certificate is available else None. returns dict if course certificate is available else None.
""" """
from lms.djangoapps.courseware.courses import get_course_by_id
if enrollment_mode == CourseMode.AUDIT: if enrollment_mode == CourseMode.AUDIT:
return CertData( return CertData(
...@@ -926,24 +929,28 @@ def _get_cert_data(student, course, course_key, is_active, enrollment_mode): ...@@ -926,24 +929,28 @@ def _get_cert_data(student, course, course_key, is_active, enrollment_mode):
_('Your enrollment: Audit track'), _('Your enrollment: Audit track'),
_('You are enrolled in the audit track for this course. The audit track does not include a certificate.'), _('You are enrolled in the audit track for this course. The audit track does not include a certificate.'),
download_url=None, download_url=None,
cert_web_view_url=None cert_web_view_url=None,
may_view_certificate=None
) )
show_generate_cert_btn = ( show_message = (
is_active and CourseMode.is_eligible_for_certificate(enrollment_mode) is_active and CourseMode.is_eligible_for_certificate(enrollment_mode)
and certs_api.cert_generation_enabled(course_key) and certs_api.cert_generation_enabled(course_key)
) )
if not show_generate_cert_btn: if not show_message:
return None return None
may_view_certificate = course_key and get_course_by_id(course_key).may_certify()
if certs_api.is_certificate_invalid(student, course_key): if certs_api.is_certificate_invalid(student, course_key):
return CertData( return CertData(
CertificateStatuses.invalidated, CertificateStatuses.invalidated,
_('Your certificate has been invalidated'), _('Your certificate has been invalidated'),
_('Please contact your course team if you have any questions.'), _('Please contact your course team if you have any questions.'),
download_url=None, download_url=None,
cert_web_view_url=None cert_web_view_url=None,
may_view_certificate=None
) )
cert_downloadable_status = certs_api.certificate_downloadable_status(student, course_key) cert_downloadable_status = certs_api.certificate_downloadable_status(student, course_key)
...@@ -957,7 +964,12 @@ def _get_cert_data(student, course, course_key, is_active, enrollment_mode): ...@@ -957,7 +964,12 @@ def _get_cert_data(student, course, course_key, is_active, enrollment_mode):
cert_web_view_url = certs_api.get_certificate_url( cert_web_view_url = certs_api.get_certificate_url(
course_id=course_key, uuid=cert_downloadable_status['uuid'] course_id=course_key, uuid=cert_downloadable_status['uuid']
) )
return CertData(cert_status, title, msg, download_url=None, cert_web_view_url=cert_web_view_url) return CertData(cert_status,
title,
msg,
download_url=None,
cert_web_view_url=cert_web_view_url,
may_view_certificate=may_view_certificate)
else: else:
return CertData( return CertData(
CertificateStatuses.generating, CertificateStatuses.generating,
...@@ -967,11 +979,17 @@ def _get_cert_data(student, course, course_key, is_active, enrollment_mode): ...@@ -967,11 +979,17 @@ def _get_cert_data(student, course, course_key, is_active, enrollment_mode):
"to it will appear here and on your Dashboard when it is ready." "to it will appear here and on your Dashboard when it is ready."
), ),
download_url=None, download_url=None,
cert_web_view_url=None cert_web_view_url=None,
may_view_certificate=None
) )
return CertData( return CertData(
cert_status, title, msg, download_url=cert_downloadable_status['download_url'], cert_web_view_url=None cert_status,
title,
msg,
download_url=cert_downloadable_status['download_url'],
cert_web_view_url=None,
may_view_certificate=may_view_certificate
) )
if cert_downloadable_status['is_generating']: if cert_downloadable_status['is_generating']:
...@@ -983,7 +1001,8 @@ def _get_cert_data(student, course, course_key, is_active, enrollment_mode): ...@@ -983,7 +1001,8 @@ def _get_cert_data(student, course, course_key, is_active, enrollment_mode):
"it will appear here and on your Dashboard when it is ready." "it will appear here and on your Dashboard when it is ready."
), ),
download_url=None, download_url=None,
cert_web_view_url=None cert_web_view_url=None,
may_view_certificate=None
) )
# If the learner is in verified modes and the student did not have # If the learner is in verified modes and the student did not have
...@@ -1001,7 +1020,8 @@ def _get_cert_data(student, course, course_key, is_active, enrollment_mode): ...@@ -1001,7 +1020,8 @@ def _get_cert_data(student, course, course_key, is_active, enrollment_mode):
'verified identity.' 'verified identity.'
).format(platform_name=platform_name), ).format(platform_name=platform_name),
download_url=None, download_url=None,
cert_web_view_url=None cert_web_view_url=None,
may_view_certificate=None
) )
return CertData( return CertData(
...@@ -1009,7 +1029,8 @@ def _get_cert_data(student, course, course_key, is_active, enrollment_mode): ...@@ -1009,7 +1029,8 @@ def _get_cert_data(student, course, course_key, is_active, enrollment_mode):
_('Congratulations, you qualified for a certificate!'), _('Congratulations, you qualified for a certificate!'),
_('You can keep working for a higher grade, or request your certificate now.'), _('You can keep working for a higher grade, or request your certificate now.'),
download_url=None, download_url=None,
cert_web_view_url=None cert_web_view_url=None,
may_view_certificate=may_view_certificate
) )
...@@ -1355,7 +1376,7 @@ def _track_successful_certificate_generation(user_id, course_id): # pylint: dis ...@@ -1355,7 +1376,7 @@ def _track_successful_certificate_generation(user_id, course_id): # pylint: dis
Track a successful certificate generation event. Track a successful certificate generation event.
Arguments: Arguments:
user_id (str): The ID of the user generting the certificate. user_id (str): The ID of the user generating the certificate.
course_id (CourseKey): Identifier for the course. course_id (CourseKey): Identifier for the course.
Returns: Returns:
None None
......
...@@ -13,6 +13,7 @@ Test utilities for mobile API tests: ...@@ -13,6 +13,7 @@ Test utilities for mobile API tests:
from datetime import timedelta from datetime import timedelta
import ddt import ddt
import datetime
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils import timezone from django.utils import timezone
...@@ -39,7 +40,10 @@ class MobileAPITestCase(ModuleStoreTestCase, APITestCase): ...@@ -39,7 +40,10 @@ class MobileAPITestCase(ModuleStoreTestCase, APITestCase):
""" """
def setUp(self): def setUp(self):
super(MobileAPITestCase, self).setUp() super(MobileAPITestCase, self).setUp()
self.course = CourseFactory.create(mobile_available=True, static_asset_path="needed_for_split") self.course = CourseFactory.create(
mobile_available=True,
static_asset_path="needed_for_split",
end=datetime.datetime.now())
self.user = UserFactory.create() self.user = UserFactory.create()
self.password = 'test' self.password = 'test'
self.username = self.user.username self.username = self.user.username
......
...@@ -67,6 +67,7 @@ from django.utils.http import urlquote_plus ...@@ -67,6 +67,7 @@ from django.utils.http import urlquote_plus
<h4 class="hd hd-4 title">${certificate_data.title}</h4> <h4 class="hd hd-4 title">${certificate_data.title}</h4>
<p class="copy">${certificate_data.msg}</p> <p class="copy">${certificate_data.msg}</p>
</div> </div>
%if certificate_data.may_view_certificate:
<div class="msg-actions"> <div class="msg-actions">
%if certificate_data.cert_web_view_url: %if certificate_data.cert_web_view_url:
<a class="btn" href="${certificate_data.cert_web_view_url}" target="_blank">${_("View Certificate")} <span class="sr">${_("Opens in a new browser window")}</span></a> <a class="btn" href="${certificate_data.cert_web_view_url}" target="_blank">${_("View Certificate")} <span class="sr">${_("Opens in a new browser window")}</span></a>
...@@ -76,6 +77,7 @@ from django.utils.http import urlquote_plus ...@@ -76,6 +77,7 @@ from django.utils.http import urlquote_plus
<button class="btn generate_certs" data-endpoint="${post_url}" id="btn_generate_cert">${_('Request Certificate')}</button> <button class="btn generate_certs" data-endpoint="${post_url}" id="btn_generate_cert">${_('Request Certificate')}</button>
%endif %endif
</div> </div>
%endif
</div> </div>
</div> </div>
%endif %endif
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course_overviews', '0013_courseoverview_language'),
]
operations = [
migrations.AddField(
model_name='courseoverview',
name='certificate_available_date',
field=models.DateTimeField(default=None, null=True),
),
]
...@@ -75,6 +75,7 @@ class CourseOverview(TimeStampedModel): ...@@ -75,6 +75,7 @@ class CourseOverview(TimeStampedModel):
has_any_active_web_certificate = BooleanField(default=False) has_any_active_web_certificate = BooleanField(default=False)
cert_name_short = TextField() cert_name_short = TextField()
cert_name_long = TextField() cert_name_long = TextField()
certificate_available_date = DateTimeField(default=None, null=True)
# Grading # Grading
lowest_passing_grade = DecimalField(max_digits=5, decimal_places=2, null=True) lowest_passing_grade = DecimalField(max_digits=5, decimal_places=2, null=True)
...@@ -172,6 +173,7 @@ class CourseOverview(TimeStampedModel): ...@@ -172,6 +173,7 @@ class CourseOverview(TimeStampedModel):
course_overview.has_any_active_web_certificate = (get_active_web_certificate(course) is not None) course_overview.has_any_active_web_certificate = (get_active_web_certificate(course) is not None)
course_overview.cert_name_short = course.cert_name_short course_overview.cert_name_short = course.cert_name_short
course_overview.cert_name_long = course.cert_name_long course_overview.cert_name_long = course.cert_name_long
course_overview.certificate_available_date = course.certificate_available_date
course_overview.lowest_passing_grade = lowest_passing_grade course_overview.lowest_passing_grade = lowest_passing_grade
course_overview.end_of_course_survey_url = course.end_of_course_survey_url course_overview.end_of_course_survey_url = course.end_of_course_survey_url
...@@ -476,7 +478,8 @@ class CourseOverview(TimeStampedModel): ...@@ -476,7 +478,8 @@ class CourseOverview(TimeStampedModel):
return course_metadata_utils.may_certify_for_course( return course_metadata_utils.may_certify_for_course(
self.certificates_display_behavior, self.certificates_display_behavior,
self.certificates_show_before_end, self.certificates_show_before_end,
self.has_ended() self.has_ended(),
self.certificate_available_date
) )
@property @property
......
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