Skip to content

Commit 52f2231

Browse files
authored
fix: make the issued date displayed on previewed certificates match real certificates (#36471)
* fix: make the issued date displayed on previewed certificates match real certificates This PR fixes an inconsistency in the dates displayed on certificates previewed via Studio with "real" certificates rendered to users.
1 parent fb62eaf commit 52f2231

2 files changed

Lines changed: 123 additions & 6 deletions

File tree

lms/djangoapps/certificates/tests/test_webview_views.py

Lines changed: 114 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import datetime
55
from unittest import skipUnless
6-
from unittest.mock import patch
6+
from unittest.mock import patch, Mock
77
from urllib.parse import urlencode
88
from uuid import uuid4
99

@@ -35,6 +35,7 @@
3535
GeneratedCertificateFactory,
3636
LinkedInAddToProfileConfigurationFactory
3737
)
38+
from lms.djangoapps.certificates.views.webview import _get_user_certificate
3839
from lms.djangoapps.certificates.utils import get_certificate_url
3940
from openedx.core.djangoapps.dark_lang.models import DarkLangConfig
4041
from openedx.core.djangoapps.site_configuration.tests.test_util import (
@@ -46,9 +47,9 @@
4647
from openedx.core.lib.courses import course_image_url
4748
from openedx.core.lib.tests.assertions.events import assert_event_matches
4849
from openedx.features.name_affirmation_api.utils import get_name_affirmation_service
49-
from xmodule.data import CertificatesDisplayBehaviors # lint-amnesty, pylint: disable=wrong-import-order
50-
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
51-
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order
50+
from xmodule.data import CertificatesDisplayBehaviors
51+
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
52+
from xmodule.modulestore.tests.factories import CourseFactory
5253

5354
FEATURES_WITH_CERTS_ENABLED = settings.FEATURES.copy()
5455
FEATURES_WITH_CERTS_ENABLED['CERTIFICATES_HTML_VIEW'] = True
@@ -1707,6 +1708,115 @@ def test_verified_certificate_description(self, enable_cert_idv_requirement):
17071708
self.assertNotContains(response, 'identity of the learner has been checked and is valid')
17081709
self.assertContains(response, 'IDV disabled')
17091710

1711+
@patch("lms.djangoapps.certificates.views.webview.datetime")
1712+
def test_get_user_certificate_preview_instructor_paced_early_no_info(self, mock_datetime):
1713+
"""
1714+
A test to verify that the _get_user_certificate utility function returns the expected date for an instructor
1715+
paced course with a display behavior of 'EARLY_NO_INFO' when the certificate is being previewed.
1716+
"""
1717+
expected_date = datetime.date(2024, 5, 2)
1718+
mock_datetime.now.return_value.date.return_value = expected_date
1719+
1720+
mock_request = Mock()
1721+
mock_request.user.has_perm.return_value = True
1722+
1723+
self.course.certificates_display_behavior = CertificatesDisplayBehaviors.EARLY_NO_INFO
1724+
self.course.certificate_available_date = None
1725+
self.course.self_paced = False
1726+
1727+
cert = _get_user_certificate(mock_request, self.user, self.course.id, self.course, preview_mode='verified')
1728+
assert cert.modified_date == expected_date
1729+
1730+
def test_get_user_certificate_preview_instructor_paced_end(self):
1731+
"""
1732+
A test to verify that the _get_user_certificate utility function returns the expected date for an instructor
1733+
paced course with a display behavior of 'END' when the certificate is being previewed.
1734+
"""
1735+
course_run_end_date = datetime.datetime(2024, 8, 19, 0, 0, 0)
1736+
1737+
mock_request = Mock()
1738+
mock_request.user.has_perm.return_value = True
1739+
1740+
self.course.certificates_display_behavior = CertificatesDisplayBehaviors.END
1741+
self.course.certificate_available_date = None
1742+
self.course.self_paced = False
1743+
self.course.end = course_run_end_date
1744+
1745+
cert = _get_user_certificate(mock_request, self.user, self.course.id, self.course, preview_mode='verified')
1746+
assert cert.modified_date == course_run_end_date
1747+
1748+
def test_get_user_certificate_preview_instructor_paced_end_with_date(self):
1749+
"""
1750+
A test to verify that the _get_user_certificate utility function returns the expected date for an instructor
1751+
paced course with a display behavior of 'END_WITH_DATE' when the certificate is being previewed.
1752+
"""
1753+
course_run_certificate_available_date = datetime.datetime(2024, 3, 14, 0, 0, 0)
1754+
1755+
mock_request = Mock()
1756+
mock_request.user.has_perm.return_value = True
1757+
1758+
self.course.certificates_display_behavior = CertificatesDisplayBehaviors.END_WITH_DATE
1759+
self.course.certificate_available_date = course_run_certificate_available_date
1760+
self.course.self_paced = False
1761+
1762+
cert = _get_user_certificate(mock_request, self.user, self.course.id, self.course, preview_mode='verified')
1763+
assert cert.modified_date == course_run_certificate_available_date
1764+
1765+
@patch("lms.djangoapps.certificates.views.webview.datetime")
1766+
def test_get_user_certificate_preview_self_paced(self, mock_datetime):
1767+
"""
1768+
A test to verify that the _get_user_certificate utility function returns the expected date for a self paced
1769+
course when the certificate is being previewed.
1770+
"""
1771+
expected_date = datetime.date(2024, 3, 10)
1772+
mock_datetime.now.return_value.date.return_value = expected_date
1773+
1774+
mock_request = Mock()
1775+
mock_request.user.has_perm.return_value = True
1776+
1777+
self.course.certificates_display_behavior = CertificatesDisplayBehaviors.EARLY_NO_INFO
1778+
self.course.certificate_available_date = None
1779+
self.course.self_paced = True
1780+
1781+
cert = _get_user_certificate(mock_request, self.user, self.course.id, self.course, preview_mode='verified')
1782+
assert cert.modified_date == expected_date
1783+
1784+
def test_get_user_certificate(self):
1785+
"""
1786+
A test to verify that the _get_user_certificate utility function returns the correct certificate when requested.
1787+
"""
1788+
mock_request = Mock()
1789+
1790+
cert = _get_user_certificate(mock_request, self.user, self.course.id, self.course)
1791+
assert cert.course_id == self.course.id
1792+
assert cert.user == self.user
1793+
assert cert.status == CertificateStatuses.downloadable
1794+
1795+
def test_get_user_certificate_no_eligible_cert(self):
1796+
"""
1797+
A test to verify the behavior of the _get_user_certificate utility function when there is no eligible
1798+
certificate to retrieve.
1799+
"""
1800+
self.cert.status = CertificateStatuses.unavailable
1801+
self.cert.save()
1802+
1803+
mock_request = Mock()
1804+
1805+
cert = _get_user_certificate(mock_request, self.user, self.course.id, self.course)
1806+
assert cert is None
1807+
1808+
def test_get_user_certificate_no_cert(self):
1809+
"""
1810+
A test to verify the behavior of the _get_user_certificate utility function when there is no certificate to
1811+
retrieve.
1812+
"""
1813+
GeneratedCertificate.objects.all().delete()
1814+
1815+
mock_request = Mock()
1816+
1817+
cert = _get_user_certificate(mock_request, self.user, self.course.id, self.course)
1818+
assert cert is None
1819+
17101820

17111821
class CertificateEventTests(CommonCertificatesTestCase, EventTrackingTestCase):
17121822
"""

lms/djangoapps/certificates/views/webview.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,15 +351,22 @@ def _get_user_certificate(request, user, course_key, course_overview, preview_mo
351351
"""
352352
user_certificate = None
353353
if preview_mode:
354-
# certificate is being previewed from studio
354+
# The certificate is being previewed from the CMS. When previewing a certificate the "modified date" is
355+
# displayed when rendered. We try to set the "modified date" of the artificial certificate record in such a way
356+
# that it matches the date selection logic used by the system when rendering a "real" certificate instance. See
357+
# the `display_date_for_certificate function` in the lms/djangoapps/certificates/api.py file.
355358
if request.user.has_perm(PREVIEW_CERTIFICATES, course_overview):
356359
if (
357360
course_overview.certificates_display_behavior == CertificatesDisplayBehaviors.END_WITH_DATE
358361
and course_overview.certificate_available_date
359362
and not course_overview.self_paced
360363
):
361364
modified_date = course_overview.certificate_available_date
362-
elif course_overview.certificates_display_behavior == CertificatesDisplayBehaviors.END:
365+
elif (
366+
course_overview.certificates_display_behavior == CertificatesDisplayBehaviors.END
367+
and course_overview.end
368+
and not course_overview.self_paced
369+
):
363370
modified_date = course_overview.end
364371
else:
365372
modified_date = datetime.now().date()

0 commit comments

Comments
 (0)