Commit 96cc3895 by Peter Fogg

Disable audit certificates for new audit enrollments.

Two new certificate statuses are introduced, 'audit_passing' and
'audit_notpassing'. These signal that the GeneratedCertificate is not
to be displayed as a cert to the user, and that they either passed or
did not. This allows us to retain existing grading logic, as well as
maintaining correctness in analytics and reporting.

Ineligible certificates are hidden by using the
`eligible_certificates` manager on GeneratedCertificate. Some places
in the coe (largely reporting, analytics, and management commands) use
the default `objects` manager, since they need access to all
certificates.

ECOM-3040
ECOM-3515
parent c6b21d13
...@@ -592,6 +592,18 @@ class CourseMode(models.Model): ...@@ -592,6 +592,18 @@ class CourseMode(models.Model):
modes = cls.modes_for_course(course_id) modes = cls.modes_for_course(course_id)
return min(mode.min_price for mode in modes if mode.currency.lower() == currency.lower()) return min(mode.min_price for mode in modes if mode.currency.lower() == currency.lower())
@classmethod
def is_eligible_for_certificate(cls, mode_slug):
"""
Returns whether or not the given mode_slug is eligible for a
certificate. Currently all modes other than 'audit' grant a
certificate. Note that audit enrollments which existed prior
to December 2015 *were* given certificates, so there will be
GeneratedCertificate records with mode='audit' which are
eligible.
"""
return mode_slug != cls.AUDIT
def to_tuple(self): def to_tuple(self):
""" """
Takes a mode model and turns it into a model named tuple. Takes a mode model and turns it into a model named tuple.
......
...@@ -430,3 +430,16 @@ class CourseModeModelTest(TestCase): ...@@ -430,3 +430,16 @@ class CourseModeModelTest(TestCase):
verified_mode.expiration_datetime = None verified_mode.expiration_datetime = None
self.assertFalse(verified_mode.expiration_datetime_is_explicit) self.assertFalse(verified_mode.expiration_datetime_is_explicit)
self.assertIsNone(verified_mode.expiration_datetime) self.assertIsNone(verified_mode.expiration_datetime)
@ddt.data(
(CourseMode.AUDIT, False),
(CourseMode.HONOR, True),
(CourseMode.VERIFIED, True),
(CourseMode.CREDIT_MODE, True),
(CourseMode.PROFESSIONAL, True),
(CourseMode.NO_ID_PROFESSIONAL_MODE, True),
)
@ddt.unpack
def test_eligible_for_cert(self, mode_slug, expected_eligibility):
"""Verify that non-audit modes are eligible for a cert."""
self.assertEqual(CourseMode.is_eligible_for_certificate(mode_slug), expected_eligibility)
...@@ -97,7 +97,9 @@ class Command(BaseCommand): ...@@ -97,7 +97,9 @@ class Command(BaseCommand):
cert_grades = { cert_grades = {
cert.user.username: cert.grade cert.user.username: cert.grade
for cert in list( for cert in list(
GeneratedCertificate.objects.filter(course_id=course_key).prefetch_related('user') GeneratedCertificate.objects.filter( # pylint: disable=no-member
course_id=course_key
).prefetch_related('user')
) )
} }
print "Grading students" print "Grading students"
......
...@@ -314,6 +314,8 @@ def _cert_info(user, course_overview, cert_status, course_mode): # pylint: disa ...@@ -314,6 +314,8 @@ def _cert_info(user, course_overview, cert_status, course_mode): # pylint: disa
CertificateStatuses.notpassing: 'notpassing', CertificateStatuses.notpassing: 'notpassing',
CertificateStatuses.restricted: 'restricted', CertificateStatuses.restricted: 'restricted',
CertificateStatuses.auditing: 'auditing', CertificateStatuses.auditing: 'auditing',
CertificateStatuses.audit_passing: 'auditing',
CertificateStatuses.audit_notpassing: 'auditing',
} }
default_status = 'processing' default_status = 'processing'
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -412,7 +412,7 @@ CREATE TABLE `auth_permission` ( ...@@ -412,7 +412,7 @@ CREATE TABLE `auth_permission` (
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `content_type_id` (`content_type_id`,`codename`), UNIQUE KEY `content_type_id` (`content_type_id`,`codename`),
CONSTRAINT `auth__content_type_id_508cf46651277a81_fk_django_content_type_id` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`) CONSTRAINT `auth__content_type_id_508cf46651277a81_fk_django_content_type_id` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=737 DEFAULT CHARSET=utf8; ) ENGINE=InnoDB AUTO_INCREMENT=740 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `auth_registration`; DROP TABLE IF EXISTS `auth_registration`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
...@@ -1600,7 +1600,7 @@ CREATE TABLE `django_content_type` ( ...@@ -1600,7 +1600,7 @@ CREATE TABLE `django_content_type` (
`model` varchar(100) NOT NULL, `model` varchar(100) NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `django_content_type_app_label_45f3b1d93ec8c61c_uniq` (`app_label`,`model`) UNIQUE KEY `django_content_type_app_label_45f3b1d93ec8c61c_uniq` (`app_label`,`model`)
) ENGINE=InnoDB AUTO_INCREMENT=245 DEFAULT CHARSET=utf8; ) ENGINE=InnoDB AUTO_INCREMENT=246 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `django_migrations`; DROP TABLE IF EXISTS `django_migrations`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
...@@ -1611,7 +1611,7 @@ CREATE TABLE `django_migrations` ( ...@@ -1611,7 +1611,7 @@ CREATE TABLE `django_migrations` (
`name` varchar(255) NOT NULL, `name` varchar(255) NOT NULL,
`applied` datetime(6) NOT NULL, `applied` datetime(6) NOT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=102 DEFAULT CHARSET=utf8; ) ENGINE=InnoDB AUTO_INCREMENT=103 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `django_openid_auth_association`; DROP TABLE IF EXISTS `django_openid_auth_association`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
...@@ -2718,6 +2718,18 @@ CREATE TABLE `programs_programsapiconfig` ( ...@@ -2718,6 +2718,18 @@ CREATE TABLE `programs_programsapiconfig` (
CONSTRAINT `programs_programsa_changed_by_id_b7c3b49d5c0dcd3_fk_auth_user_id` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`) CONSTRAINT `programs_programsa_changed_by_id_b7c3b49d5c0dcd3_fk_auth_user_id` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `rss_proxy_whitelistedrssurl`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `rss_proxy_whitelistedrssurl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`created` datetime(6) NOT NULL,
`modified` datetime(6) NOT NULL,
`url` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `url` (`url`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `self_paced_selfpacedconfiguration`; DROP TABLE IF EXISTS `self_paced_selfpacedconfiguration`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */; /*!40101 SET character_set_client = utf8 */;
......
...@@ -78,7 +78,7 @@ def get_certificates_for_user(username): ...@@ -78,7 +78,7 @@ def get_certificates_for_user(username):
else None else None
), ),
} }
for cert in GeneratedCertificate.objects.filter(user__username=username).order_by("course_id") for cert in GeneratedCertificate.eligible_certificates.filter(user__username=username).order_by("course_id")
] ]
...@@ -109,11 +109,14 @@ def generate_user_certificates(student, course_key, course=None, insecure=False, ...@@ -109,11 +109,14 @@ def generate_user_certificates(student, course_key, course=None, insecure=False,
if insecure: if insecure:
xqueue.use_https = False xqueue.use_https = False
generate_pdf = not has_html_certificates_enabled(course_key, course) generate_pdf = not has_html_certificates_enabled(course_key, course)
status, cert = xqueue.add_cert(student, course_key, cert = xqueue.add_cert(
course=course, student,
generate_pdf=generate_pdf, course_key,
forced_grade=forced_grade) course=course,
if status in [CertificateStatuses.generating, CertificateStatuses.downloadable]: generate_pdf=generate_pdf,
forced_grade=forced_grade
)
if cert.status in [CertificateStatuses.generating, CertificateStatuses.downloadable]:
emit_certificate_event('created', student, course_key, course, { emit_certificate_event('created', student, course_key, course, {
'user_id': student.id, 'user_id': student.id,
'course_id': unicode(course_key), 'course_id': unicode(course_key),
...@@ -121,7 +124,7 @@ def generate_user_certificates(student, course_key, course=None, insecure=False, ...@@ -121,7 +124,7 @@ def generate_user_certificates(student, course_key, course=None, insecure=False,
'enrollment_mode': cert.mode, 'enrollment_mode': cert.mode,
'generation_mode': generation_mode 'generation_mode': generation_mode
}) })
return status return cert.status
def regenerate_user_certificates(student, course_key, course=None, def regenerate_user_certificates(student, course_key, course=None,
...@@ -385,7 +388,7 @@ def get_certificate_url(user_id=None, course_id=None, uuid=None): ...@@ -385,7 +388,7 @@ def get_certificate_url(user_id=None, course_id=None, uuid=None):
) )
return url return url
try: try:
user_certificate = GeneratedCertificate.objects.get( user_certificate = GeneratedCertificate.eligible_certificates.get(
user=user_id, user=user_id,
course_id=course_id course_id=course_id
) )
......
...@@ -76,7 +76,7 @@ class Command(BaseCommand): ...@@ -76,7 +76,7 @@ class Command(BaseCommand):
status = options.get('status', CertificateStatuses.downloadable) status = options.get('status', CertificateStatuses.downloadable)
grade = options.get('grade', '') grade = options.get('grade', '')
cert, created = GeneratedCertificate.objects.get_or_create( cert, created = GeneratedCertificate.eligible_certificates.get_or_create(
user=user, user=user,
course_id=course_key course_id=course_key
) )
......
...@@ -42,8 +42,9 @@ class Command(BaseCommand): ...@@ -42,8 +42,9 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
course_id = options['course'] course_id = options['course']
print "Fetching ungraded students for {0}".format(course_id) print "Fetching ungraded students for {0}".format(course_id)
ungraded = GeneratedCertificate.objects.filter( ungraded = GeneratedCertificate.objects.filter( # pylint: disable=no-member
course_id__exact=course_id).filter(grade__exact='') course_id__exact=course_id
).filter(grade__exact='')
course = courses.get_course_by_id(course_id) course = courses.get_course_by_id(course_id)
factory = RequestFactory() factory = RequestFactory()
request = factory.get('/') request = factory.get('/')
......
...@@ -70,14 +70,17 @@ class Command(BaseCommand): ...@@ -70,14 +70,17 @@ class Command(BaseCommand):
enrolled_total = User.objects.filter( enrolled_total = User.objects.filter(
courseenrollment__course_id=course_id courseenrollment__course_id=course_id
) )
verified_enrolled = GeneratedCertificate.objects.filter( verified_enrolled = GeneratedCertificate.objects.filter( # pylint: disable=no-member
course_id__exact=course_id, mode__exact='verified' course_id__exact=course_id,
mode__exact='verified'
) )
honor_enrolled = GeneratedCertificate.objects.filter( honor_enrolled = GeneratedCertificate.objects.filter( # pylint: disable=no-member
course_id__exact=course_id, mode__exact='honor' course_id__exact=course_id,
mode__exact='honor'
) )
audit_enrolled = GeneratedCertificate.objects.filter( audit_enrolled = GeneratedCertificate.objects.filter( # pylint: disable=no-member
course_id__exact=course_id, mode__exact='audit' course_id__exact=course_id,
mode__exact='audit'
) )
cert_data[course_id] = { cert_data[course_id] = {
...@@ -88,7 +91,7 @@ class Command(BaseCommand): ...@@ -88,7 +91,7 @@ class Command(BaseCommand):
'audit_enrolled': audit_enrolled.count() 'audit_enrolled': audit_enrolled.count()
} }
status_tally = GeneratedCertificate.objects.filter( status_tally = GeneratedCertificate.objects.filter( # pylint: disable=no-member
course_id__exact=course_id course_id__exact=course_id
).values('status').annotate( ).values('status').annotate(
dcount=Count('status') dcount=Count('status')
...@@ -100,7 +103,7 @@ class Command(BaseCommand): ...@@ -100,7 +103,7 @@ class Command(BaseCommand):
} }
) )
mode_tally = GeneratedCertificate.objects.filter( mode_tally = GeneratedCertificate.objects.filter( # pylint: disable=no-member
course_id__exact=course_id, course_id__exact=course_id,
status__exact='downloadable' status__exact='downloadable'
).values('mode').annotate( ).values('mode').annotate(
......
...@@ -81,7 +81,7 @@ class Command(BaseCommand): ...@@ -81,7 +81,7 @@ class Command(BaseCommand):
# Retrieve the IDs of generated certificates with # Retrieve the IDs of generated certificates with
# error status in the set of courses we're considering. # error status in the set of courses we're considering.
queryset = ( queryset = (
GeneratedCertificate.objects.select_related('user') GeneratedCertificate.objects.select_related('user') # pylint: disable=no-member
).filter(status=CertificateStatuses.error) ).filter(status=CertificateStatuses.error)
if only_course_keys: if only_course_keys:
queryset = queryset.filter(course_id__in=only_course_keys) queryset = queryset.filter(course_id__in=only_course_keys)
......
...@@ -86,6 +86,8 @@ class CertificateStatuses(object): ...@@ -86,6 +86,8 @@ class CertificateStatuses(object):
restricted = 'restricted' restricted = 'restricted'
unavailable = 'unavailable' unavailable = 'unavailable'
auditing = 'auditing' auditing = 'auditing'
audit_passing = 'audit_passing'
audit_notpassing = 'audit_notpassing'
readable_statuses = { readable_statuses = {
downloadable: "already received", downloadable: "already received",
...@@ -143,7 +145,7 @@ class CertificateWhitelist(models.Model): ...@@ -143,7 +145,7 @@ class CertificateWhitelist(models.Model):
if student: if student:
white_list = white_list.filter(user=student) white_list = white_list.filter(user=student)
result = [] result = []
generated_certificates = GeneratedCertificate.objects.filter( generated_certificates = GeneratedCertificate.eligible_certificates.filter(
course_id=course_id, course_id=course_id,
user__in=[exception.user for exception in white_list], user__in=[exception.user for exception in white_list],
status=CertificateStatuses.downloadable status=CertificateStatuses.downloadable
...@@ -168,11 +170,42 @@ class CertificateWhitelist(models.Model): ...@@ -168,11 +170,42 @@ class CertificateWhitelist(models.Model):
return result return result
class EligibleCertificateManager(models.Manager):
"""
A manager for `GeneratedCertificate` models that automatically
filters out ineligible certs.
The idea is to prevent accidentally granting certificates to
students who have not enrolled in a cert-granting mode. The
alternative is to filter by eligible_for_certificate=True every
time certs are searched for, which is verbose and likely to be
forgotten.
"""
def get_queryset(self):
"""
Return a queryset for `GeneratedCertificate` models, filtering out
ineligible certificates.
"""
return super(EligibleCertificateManager, self).get_queryset().exclude(
status__in=(CertificateStatuses.audit_passing, CertificateStatuses.audit_notpassing)
)
class GeneratedCertificate(models.Model): class GeneratedCertificate(models.Model):
""" """
Base model for generated certificates Base model for generated certificates
""" """
# Only returns eligible certificates. This should be used in
# preference to the default `objects` manager in most cases.
eligible_certificates = EligibleCertificateManager()
# Normal object manager, which should only be used when ineligible
# certificates (i.e. new audit certs) should be included in the
# results. Django requires us to explicitly declare this.
objects = models.Manager()
MODES = Choices('verified', 'honor', 'audit', 'professional', 'no-id-professional') MODES = Choices('verified', 'honor', 'audit', 'professional', 'no-id-professional')
VERIFIED_CERTS_MODES = [CourseMode.VERIFIED, CourseMode.CREDIT_MODE] VERIFIED_CERTS_MODES = [CourseMode.VERIFIED, CourseMode.CREDIT_MODE]
...@@ -410,7 +443,7 @@ def certificate_status_for_student(student, course_id): ...@@ -410,7 +443,7 @@ def certificate_status_for_student(student, course_id):
''' '''
try: try:
generated_certificate = GeneratedCertificate.objects.get( generated_certificate = GeneratedCertificate.objects.get( # pylint: disable=no-member
user=student, course_id=course_id) user=student, course_id=course_id)
cert_status = { cert_status = {
'status': generated_certificate.status, 'status': generated_certificate.status,
......
...@@ -231,7 +231,7 @@ class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, Modu ...@@ -231,7 +231,7 @@ class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, Modu
certs_api.generate_user_certificates(self.student, self.course.id) certs_api.generate_user_certificates(self.student, self.course.id)
# Verify that the certificate has status 'generating' # Verify that the certificate has status 'generating'
cert = GeneratedCertificate.objects.get(user=self.student, course_id=self.course.id) cert = GeneratedCertificate.eligible_certificates.get(user=self.student, course_id=self.course.id)
self.assertEqual(cert.status, CertificateStatuses.generating) self.assertEqual(cert.status, CertificateStatuses.generating)
self.assert_event_emitted( self.assert_event_emitted(
'edx.certificate.created', 'edx.certificate.created',
...@@ -249,7 +249,7 @@ class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, Modu ...@@ -249,7 +249,7 @@ class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, Modu
certs_api.generate_user_certificates(self.student, self.course.id) certs_api.generate_user_certificates(self.student, self.course.id)
# Verify that the certificate has been marked with status error # Verify that the certificate has been marked with status error
cert = GeneratedCertificate.objects.get(user=self.student, course_id=self.course.id) cert = GeneratedCertificate.eligible_certificates.get(user=self.student, course_id=self.course.id)
self.assertEqual(cert.status, 'error') self.assertEqual(cert.status, 'error')
self.assertIn(self.ERROR_REASON, cert.error_reason) self.assertIn(self.ERROR_REASON, cert.error_reason)
...@@ -263,7 +263,7 @@ class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, Modu ...@@ -263,7 +263,7 @@ class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, Modu
certs_api.generate_user_certificates(self.student, self.course.id) certs_api.generate_user_certificates(self.student, self.course.id)
# Verify that the certificate has status 'downloadable' # Verify that the certificate has status 'downloadable'
cert = GeneratedCertificate.objects.get(user=self.student, course_id=self.course.id) cert = GeneratedCertificate.eligible_certificates.get(user=self.student, course_id=self.course.id)
self.assertEqual(cert.status, CertificateStatuses.downloadable) self.assertEqual(cert.status, CertificateStatuses.downloadable)
@patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': False}) @patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': False})
......
...@@ -6,6 +6,7 @@ from nose.plugins.attrib import attr ...@@ -6,6 +6,7 @@ from nose.plugins.attrib import attr
from django.test.utils import override_settings from django.test.utils import override_settings
from mock import patch from mock import patch
from course_modes.models import CourseMode
from opaque_keys.edx.locator import CourseLocator from opaque_keys.edx.locator import CourseLocator
from certificates.tests.factories import BadgeAssertionFactory from certificates.tests.factories import BadgeAssertionFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
...@@ -30,16 +31,17 @@ class CertificateManagementTest(ModuleStoreTestCase): ...@@ -30,16 +31,17 @@ class CertificateManagementTest(ModuleStoreTestCase):
for __ in range(3) for __ in range(3)
] ]
def _create_cert(self, course_key, user, status): def _create_cert(self, course_key, user, status, mode=CourseMode.HONOR):
"""Create a certificate entry. """ """Create a certificate entry. """
# Enroll the user in the course # Enroll the user in the course
CourseEnrollmentFactory.create( CourseEnrollmentFactory.create(
user=user, user=user,
course_id=course_key course_id=course_key,
mode=mode
) )
# Create the certificate # Create the certificate
GeneratedCertificate.objects.create( GeneratedCertificate.eligible_certificates.create(
user=user, user=user,
course_id=course_key, course_id=course_key,
status=status status=status
...@@ -52,7 +54,7 @@ class CertificateManagementTest(ModuleStoreTestCase): ...@@ -52,7 +54,7 @@ class CertificateManagementTest(ModuleStoreTestCase):
def _assert_cert_status(self, course_key, user, expected_status): def _assert_cert_status(self, course_key, user, expected_status):
"""Check the status of a certificate. """ """Check the status of a certificate. """
cert = GeneratedCertificate.objects.get(user=user, course_id=course_key) cert = GeneratedCertificate.eligible_certificates.get(user=user, course_id=course_key)
self.assertEqual(cert.status, expected_status) self.assertEqual(cert.status, expected_status)
...@@ -61,9 +63,10 @@ class CertificateManagementTest(ModuleStoreTestCase): ...@@ -61,9 +63,10 @@ class CertificateManagementTest(ModuleStoreTestCase):
class ResubmitErrorCertificatesTest(CertificateManagementTest): class ResubmitErrorCertificatesTest(CertificateManagementTest):
"""Tests for the resubmit_error_certificates management command. """ """Tests for the resubmit_error_certificates management command. """
def test_resubmit_error_certificate(self): @ddt.data(CourseMode.HONOR, CourseMode.VERIFIED)
def test_resubmit_error_certificate(self, mode):
# Create a certificate with status 'error' # Create a certificate with status 'error'
self._create_cert(self.courses[0].id, self.user, CertificateStatuses.error) self._create_cert(self.courses[0].id, self.user, CertificateStatuses.error, mode)
# Re-submit all certificates with status 'error' # Re-submit all certificates with status 'error'
with check_mongo_calls(1): with check_mongo_calls(1):
...@@ -198,7 +201,7 @@ class RegenerateCertificatesTest(CertificateManagementTest): ...@@ -198,7 +201,7 @@ class RegenerateCertificatesTest(CertificateManagementTest):
username=self.user.email, course=unicode(key), noop=False, insecure=True, template_file=None, username=self.user.email, course=unicode(key), noop=False, insecure=True, template_file=None,
grade_value=None grade_value=None
) )
certificate = GeneratedCertificate.objects.get( certificate = GeneratedCertificate.eligible_certificates.get(
user=self.user, user=self.user,
course_id=key course_id=key
) )
...@@ -236,7 +239,7 @@ class UngenerateCertificatesTest(CertificateManagementTest): ...@@ -236,7 +239,7 @@ class UngenerateCertificatesTest(CertificateManagementTest):
course=unicode(key), noop=False, insecure=True, force=False course=unicode(key), noop=False, insecure=True, force=False
) )
self.assertTrue(mock_send_to_queue.called) self.assertTrue(mock_send_to_queue.called)
certificate = GeneratedCertificate.objects.get( certificate = GeneratedCertificate.eligible_certificates.get(
user=self.user, user=self.user,
course_id=key course_id=key
) )
......
...@@ -28,7 +28,7 @@ class CreateFakeCertTest(TestCase): ...@@ -28,7 +28,7 @@ class CreateFakeCertTest(TestCase):
cert_mode='verified', cert_mode='verified',
grade='0.89' grade='0.89'
) )
cert = GeneratedCertificate.objects.get(user=self.user, course_id=self.COURSE_KEY) cert = GeneratedCertificate.eligible_certificates.get(user=self.user, course_id=self.COURSE_KEY)
self.assertEqual(cert.status, 'downloadable') self.assertEqual(cert.status, 'downloadable')
self.assertEqual(cert.mode, 'verified') self.assertEqual(cert.mode, 'verified')
self.assertEqual(cert.grade, '0.89') self.assertEqual(cert.grade, '0.89')
...@@ -41,7 +41,7 @@ class CreateFakeCertTest(TestCase): ...@@ -41,7 +41,7 @@ class CreateFakeCertTest(TestCase):
unicode(self.COURSE_KEY), unicode(self.COURSE_KEY),
cert_mode='honor' cert_mode='honor'
) )
cert = GeneratedCertificate.objects.get(user=self.user, course_id=self.COURSE_KEY) cert = GeneratedCertificate.eligible_certificates.get(user=self.user, course_id=self.COURSE_KEY)
self.assertEqual(cert.mode, 'honor') self.assertEqual(cert.mode, 'honor')
def test_too_few_args(self): def test_too_few_args(self):
......
...@@ -8,13 +8,21 @@ from django.test.utils import override_settings ...@@ -8,13 +8,21 @@ from django.test.utils import override_settings
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from path import Path as path from path import Path as path
from opaque_keys.edx.locator import CourseLocator
from certificates.models import ( from certificates.models import (
ExampleCertificate, ExampleCertificate,
ExampleCertificateSet, ExampleCertificateSet,
CertificateHtmlViewConfiguration, CertificateHtmlViewConfiguration,
CertificateTemplateAsset, CertificateTemplateAsset,
BadgeImageConfiguration) BadgeImageConfiguration,
EligibleCertificateManager,
GeneratedCertificate,
CertificateStatuses,
)
from certificates.tests.factories import GeneratedCertificateFactory
from opaque_keys.edx.locator import CourseLocator
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
FEATURES_INVALID_FILE_PATH = settings.FEATURES.copy() FEATURES_INVALID_FILE_PATH = settings.FEATURES.copy()
FEATURES_INVALID_FILE_PATH['CERTS_HTML_VIEW_CONFIG_PATH'] = 'invalid/path/to/config.json' FEATURES_INVALID_FILE_PATH['CERTS_HTML_VIEW_CONFIG_PATH'] = 'invalid/path/to/config.json'
...@@ -234,3 +242,42 @@ class CertificateTemplateAssetTest(TestCase): ...@@ -234,3 +242,42 @@ class CertificateTemplateAssetTest(TestCase):
certificate_template_asset = CertificateTemplateAsset.objects.get(id=1) certificate_template_asset = CertificateTemplateAsset.objects.get(id=1)
self.assertEqual(certificate_template_asset.asset, 'certificate_template_assets/1/picture2.jpg') self.assertEqual(certificate_template_asset.asset, 'certificate_template_assets/1/picture2.jpg')
@attr('shard_1')
class EligibleCertificateManagerTest(SharedModuleStoreTestCase):
"""
Test the GeneratedCertificate model's object manager for filtering
out ineligible certs.
"""
@classmethod
def setUpClass(cls):
super(EligibleCertificateManagerTest, cls).setUpClass()
cls.courses = (CourseFactory(), CourseFactory())
def setUp(self):
super(EligibleCertificateManagerTest, self).setUp()
self.user = UserFactory()
self.eligible_cert = GeneratedCertificateFactory.create(
status=CertificateStatuses.downloadable,
user=self.user,
course_id=self.courses[0].id # pylint: disable=no-member
)
self.ineligible_cert = GeneratedCertificateFactory.create(
status=CertificateStatuses.audit_passing,
user=self.user,
course_id=self.courses[1].id # pylint: disable=no-member
)
def test_filter_ineligible_certificates(self):
"""
Verify that the EligibleCertificateManager filters out
certificates marked as ineligible, and that the default object
manager for GeneratedCertificate does not filter them out.
"""
self.assertEqual(list(GeneratedCertificate.eligible_certificates.filter(user=self.user)), [self.eligible_cert])
self.assertEqual(
list(GeneratedCertificate.objects.filter(user=self.user)), # pylint: disable=no-member
[self.eligible_cert, self.ineligible_cert]
)
...@@ -9,6 +9,7 @@ from nose.plugins.attrib import attr ...@@ -9,6 +9,7 @@ from nose.plugins.attrib import attr
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from course_modes.models import CourseMode
from opaque_keys.edx.locator import CourseLocator from opaque_keys.edx.locator import CourseLocator
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from student.tests.factories import UserFactory, CourseEnrollmentFactory from student.tests.factories import UserFactory, CourseEnrollmentFactory
...@@ -22,13 +23,14 @@ from xmodule.modulestore.tests.factories import CourseFactory ...@@ -22,13 +23,14 @@ from xmodule.modulestore.tests.factories import CourseFactory
# in our `XQueueCertInterface` implementation. # in our `XQueueCertInterface` implementation.
from capa.xqueue_interface import XQueueInterface from capa.xqueue_interface import XQueueInterface
from certificates.queue import XQueueCertInterface
from certificates.models import ( from certificates.models import (
ExampleCertificateSet, ExampleCertificateSet,
ExampleCertificate, ExampleCertificate,
GeneratedCertificate, GeneratedCertificate,
CertificateStatuses, CertificateStatuses,
) )
from certificates.queue import XQueueCertInterface
from certificates.tests.factories import CertificateWhitelistFactory, GeneratedCertificateFactory
from lms.djangoapps.verify_student.tests.factories import SoftwareSecurePhotoVerificationFactory from lms.djangoapps.verify_student.tests.factories import SoftwareSecurePhotoVerificationFactory
...@@ -74,7 +76,7 @@ class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase): ...@@ -74,7 +76,7 @@ class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase):
# Verify that add_cert method does not add message to queue # Verify that add_cert method does not add message to queue
self.assertFalse(mock_send.called) self.assertFalse(mock_send.called)
certificate = GeneratedCertificate.objects.get(user=self.user, course_id=self.course.id) certificate = GeneratedCertificate.eligible_certificates.get(user=self.user, course_id=self.course.id)
self.assertEqual(certificate.status, CertificateStatuses.downloadable) self.assertEqual(certificate.status, CertificateStatuses.downloadable)
self.assertIsNotNone(certificate.verify_uuid) self.assertIsNotNone(certificate.verify_uuid)
...@@ -84,7 +86,11 @@ class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase): ...@@ -84,7 +86,11 @@ class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase):
template_name = 'certificate-template-{id.org}-{id.course}.pdf'.format( template_name = 'certificate-template-{id.org}-{id.course}.pdf'.format(
id=self.course.id id=self.course.id
) )
self.assert_queue_response(mode, mode, template_name) mock_send = self.add_cert_to_queue(mode)
if CourseMode.is_eligible_for_certificate(mode):
self.assert_certificate_generated(mock_send, mode, template_name)
else:
self.assert_ineligible_certificate_generated(mock_send, mode)
@ddt.data('credit', 'verified') @ddt.data('credit', 'verified')
def test_add_cert_with_verified_certificates(self, mode): def test_add_cert_with_verified_certificates(self, mode):
...@@ -95,10 +101,40 @@ class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase): ...@@ -95,10 +101,40 @@ class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase):
id=self.course.id id=self.course.id
) )
self.assert_queue_response(mode, 'verified', template_name) mock_send = self.add_cert_to_queue(mode)
self.assert_certificate_generated(mock_send, 'verified', template_name)
def test_ineligible_cert_whitelisted(self):
"""Test that audit mode students can receive a certificate if they are whitelisted."""
# Enroll as audit
CourseEnrollmentFactory(
user=self.user_2,
course_id=self.course.id,
is_active=True,
mode='audit'
)
# Whitelist student
CertificateWhitelistFactory(course_id=self.course.id, user=self.user_2)
# Generate certs
with patch('courseware.grades.grade', Mock(return_value={'grade': 'Pass', 'percent': 0.75})):
with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
mock_send.return_value = (0, None)
self.xqueue.add_cert(self.user_2, self.course.id)
# Assert cert generated correctly
self.assertTrue(mock_send.called)
certificate = GeneratedCertificate.certificate_for_student(self.user_2, self.course.id)
self.assertIsNotNone(certificate)
self.assertEqual(certificate.mode, 'audit')
def assert_queue_response(self, mode, expected_mode, expected_template_name): def add_cert_to_queue(self, mode):
"""Dry method for course enrollment and adding request to queue.""" """
Dry method for course enrollment and adding request to
queue. Returns a mock object containing information about the
`XQueueInterface.send_to_queue` method, which can be used in other
assertions.
"""
CourseEnrollmentFactory( CourseEnrollmentFactory(
user=self.user_2, user=self.user_2,
course_id=self.course.id, course_id=self.course.id,
...@@ -109,19 +145,97 @@ class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase): ...@@ -109,19 +145,97 @@ class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase):
with patch.object(XQueueInterface, 'send_to_queue') as mock_send: with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
mock_send.return_value = (0, None) mock_send.return_value = (0, None)
self.xqueue.add_cert(self.user_2, self.course.id) self.xqueue.add_cert(self.user_2, self.course.id)
return mock_send
def assert_certificate_generated(self, mock_send, expected_mode, expected_template_name):
"""
Assert that a certificate was generated with the correct mode and
template type.
"""
# Verify that the task was sent to the queue with the correct callback URL # Verify that the task was sent to the queue with the correct callback URL
self.assertTrue(mock_send.called) self.assertTrue(mock_send.called)
__, kwargs = mock_send.call_args_list[0] __, kwargs = mock_send.call_args_list[0]
actual_header = json.loads(kwargs['header']) actual_header = json.loads(kwargs['header'])
self.assertIn('https://edx.org/update_certificate?key=', actual_header['lms_callback_url']) self.assertIn('https://edx.org/update_certificate?key=', actual_header['lms_callback_url'])
certificate = GeneratedCertificate.objects.get(user=self.user_2, course_id=self.course.id)
self.assertEqual(certificate.mode, expected_mode)
body = json.loads(kwargs['body']) body = json.loads(kwargs['body'])
self.assertIn(expected_template_name, body['template_pdf']) self.assertIn(expected_template_name, body['template_pdf'])
certificate = GeneratedCertificate.eligible_certificates.get(user=self.user_2, course_id=self.course.id)
self.assertEqual(certificate.mode, expected_mode)
def assert_ineligible_certificate_generated(self, mock_send, expected_mode):
"""
Assert that an ineligible certificate was generated with the
correct mode.
"""
# Ensure the certificate was not generated
self.assertFalse(mock_send.called)
certificate = GeneratedCertificate.objects.get( # pylint: disable=no-member
user=self.user_2,
course_id=self.course.id
)
self.assertIn(certificate.status, (CertificateStatuses.audit_passing, CertificateStatuses.audit_notpassing))
self.assertEqual(certificate.mode, expected_mode)
@ddt.data(
(CertificateStatuses.restricted, False),
(CertificateStatuses.deleting, False),
(CertificateStatuses.generating, True),
(CertificateStatuses.unavailable, True),
(CertificateStatuses.deleted, True),
(CertificateStatuses.error, True),
(CertificateStatuses.notpassing, True),
(CertificateStatuses.downloadable, True),
(CertificateStatuses.auditing, True),
)
@ddt.unpack
def test_add_cert_statuses(self, status, should_generate):
"""
Test that certificates can or cannot be generated with the given
certificate status.
"""
with patch('certificates.queue.certificate_status_for_student', Mock(return_value={'status': status})):
mock_send = self.add_cert_to_queue('verified')
if should_generate:
self.assertTrue(mock_send.called)
else:
self.assertFalse(mock_send.called)
def test_regen_audit_certs_eligibility(self):
"""
Test that existing audit certificates remain eligible even if cert
generation is re-run.
"""
# Create an existing audit enrollment and certificate
CourseEnrollmentFactory(
user=self.user_2,
course_id=self.course.id,
is_active=True,
mode=CourseMode.AUDIT,
)
GeneratedCertificateFactory(
user=self.user_2,
course_id=self.course.id,
grade='1.0',
status=CertificateStatuses.downloadable,
mode=GeneratedCertificate.MODES.audit,
)
# Run grading/cert generation again
with patch('courseware.grades.grade', Mock(return_value={'grade': 'Pass', 'percent': 0.75})):
with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
mock_send.return_value = (0, None)
self.xqueue.add_cert(self.user_2, self.course.id)
self.assertEqual(
GeneratedCertificate.objects.get(user=self.user_2, course_id=self.course.id).status, # pylint: disable=no-member
CertificateStatuses.generating
)
@attr('shard_1') @attr('shard_1')
@override_settings(CERT_QUEUE='certificates') @override_settings(CERT_QUEUE='certificates')
......
...@@ -71,7 +71,7 @@ class CertificateSupportTestCase(ModuleStoreTestCase): ...@@ -71,7 +71,7 @@ class CertificateSupportTestCase(ModuleStoreTestCase):
) )
# Create certificates for the student # Create certificates for the student
self.cert = GeneratedCertificate.objects.create( self.cert = GeneratedCertificate.eligible_certificates.create(
user=self.student, user=self.student,
course_id=self.CERT_COURSE_KEY, course_id=self.CERT_COURSE_KEY,
grade=self.CERT_GRADE, grade=self.CERT_GRADE,
...@@ -259,7 +259,7 @@ class CertificateRegenerateTests(CertificateSupportTestCase): ...@@ -259,7 +259,7 @@ class CertificateRegenerateTests(CertificateSupportTestCase):
# Check that the user's certificate was updated # Check that the user's certificate was updated
# Since the student hasn't actually passed the course, # Since the student hasn't actually passed the course,
# we'd expect that the certificate status will be "notpassing" # we'd expect that the certificate status will be "notpassing"
cert = GeneratedCertificate.objects.get(user=self.student) cert = GeneratedCertificate.eligible_certificates.get(user=self.student)
self.assertEqual(cert.status, CertificateStatuses.notpassing) self.assertEqual(cert.status, CertificateStatuses.notpassing)
def test_regenerate_certificate_missing_params(self): def test_regenerate_certificate_missing_params(self):
...@@ -298,7 +298,7 @@ class CertificateRegenerateTests(CertificateSupportTestCase): ...@@ -298,7 +298,7 @@ class CertificateRegenerateTests(CertificateSupportTestCase):
def test_regenerate_user_has_no_certificate(self): def test_regenerate_user_has_no_certificate(self):
# Delete the user's certificate # Delete the user's certificate
GeneratedCertificate.objects.all().delete() GeneratedCertificate.eligible_certificates.all().delete()
# Should be able to regenerate # Should be able to regenerate
response = self._regenerate( response = self._regenerate(
...@@ -308,7 +308,7 @@ class CertificateRegenerateTests(CertificateSupportTestCase): ...@@ -308,7 +308,7 @@ class CertificateRegenerateTests(CertificateSupportTestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
# A new certificate is created # A new certificate is created
num_certs = GeneratedCertificate.objects.filter(user=self.student).count() num_certs = GeneratedCertificate.eligible_certificates.filter(user=self.student).count()
self.assertEqual(num_certs, 1) self.assertEqual(num_certs, 1)
def _regenerate(self, course_key=None, username=None): def _regenerate(self, course_key=None, username=None):
...@@ -412,7 +412,7 @@ class CertificateGenerateTests(CertificateSupportTestCase): ...@@ -412,7 +412,7 @@ class CertificateGenerateTests(CertificateSupportTestCase):
def test_generate_user_has_no_certificate(self): def test_generate_user_has_no_certificate(self):
# Delete the user's certificate # Delete the user's certificate
GeneratedCertificate.objects.all().delete() GeneratedCertificate.eligible_certificates.all().delete()
# Should be able to generate # Should be able to generate
response = self._generate( response = self._generate(
...@@ -422,7 +422,7 @@ class CertificateGenerateTests(CertificateSupportTestCase): ...@@ -422,7 +422,7 @@ class CertificateGenerateTests(CertificateSupportTestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
# A new certificate is created # A new certificate is created
num_certs = GeneratedCertificate.objects.filter(user=self.student).count() num_certs = GeneratedCertificate.eligible_certificates.filter(user=self.student).count()
self.assertEqual(num_certs, 1) self.assertEqual(num_certs, 1)
def _generate(self, course_key=None, username=None): def _generate(self, course_key=None, username=None):
......
...@@ -210,7 +210,7 @@ class MicrositeCertificatesViewsTests(ModuleStoreTestCase): ...@@ -210,7 +210,7 @@ class MicrositeCertificatesViewsTests(ModuleStoreTestCase):
self.user.profile.name = "Joe User" self.user.profile.name = "Joe User"
self.user.profile.save() self.user.profile.save()
self.client.login(username=self.user.username, password='foo') self.client.login(username=self.user.username, password='foo')
self.cert = GeneratedCertificate.objects.create( self.cert = GeneratedCertificate.eligible_certificates.create(
user=self.user, user=self.user,
course_id=self.course_id, course_id=self.course_id,
download_uuid=uuid4(), download_uuid=uuid4(),
......
...@@ -13,6 +13,7 @@ from django.core.urlresolvers import reverse ...@@ -13,6 +13,7 @@ from django.core.urlresolvers import reverse
from django.test.client import Client from django.test.client import Client
from django.test.utils import override_settings from django.test.utils import override_settings
from course_modes.models import CourseMode
from openedx.core.lib.tests.assertions.events import assert_event_matches from openedx.core.lib.tests.assertions.events import assert_event_matches
from student.tests.factories import UserFactory, CourseEnrollmentFactory from student.tests.factories import UserFactory, CourseEnrollmentFactory
from student.roles import CourseStaffRole from student.roles import CourseStaffRole
...@@ -96,7 +97,8 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase): ...@@ -96,7 +97,8 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
) )
CourseEnrollmentFactory.create( CourseEnrollmentFactory.create(
user=self.user, user=self.user,
course_id=self.course_id course_id=self.course_id,
mode=CourseMode.HONOR,
) )
CertificateHtmlViewConfigurationFactory.create() CertificateHtmlViewConfigurationFactory.create()
LinkedInAddToProfileConfigurationFactory.create() LinkedInAddToProfileConfigurationFactory.create()
...@@ -378,6 +380,38 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase): ...@@ -378,6 +380,38 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
self.assertIn("Cannot Find Certificate", response.content) self.assertIn("Cannot Find Certificate", response.content)
self.assertIn("We cannot find a certificate with this URL or ID number.", response.content) self.assertIn("We cannot find a certificate with this URL or ID number.", response.content)
@ddt.data(
(CertificateStatuses.downloadable, True),
(CertificateStatuses.audit_passing, False),
(CertificateStatuses.audit_notpassing, False),
)
@ddt.unpack
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
def test_audit_certificate_display(self, status, eligible_for_certificate):
"""
Ensure that audit-mode certs are only shown in the web view if they
are eligible for a certificate.
"""
# Convert the cert to audit, with the specified eligibility
self.cert.mode = 'audit'
self.cert.status = status
self.cert.save()
self._add_course_certificates(count=1, signatory_count=2)
test_url = get_certificate_url(
user_id=self.user.id,
course_id=unicode(self.course.id)
)
response = self.client.get(test_url)
if eligible_for_certificate:
self.assertIn(str(self.cert.verify_uuid), response.content)
else:
self.assertIn("Invalid Certificate", response.content)
self.assertIn("Cannot Find Certificate", response.content)
self.assertIn("We cannot find a certificate with this URL or ID number.", response.content)
self.assertNotIn(str(self.cert.verify_uuid), response.content)
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED) @override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
def test_html_view_for_invalid_certificate(self): def test_html_view_for_invalid_certificate(self):
""" """
...@@ -533,7 +567,7 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase): ...@@ -533,7 +567,7 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
course_id=unicode(self.course.id) course_id=unicode(self.course.id)
) )
self.cert.delete() self.cert.delete()
self.assertEqual(len(GeneratedCertificate.objects.all()), 0) self.assertEqual(len(GeneratedCertificate.eligible_certificates.all()), 0)
response = self.client.get(test_url) response = self.client.get(test_url)
self.assertIn('invalid', response.content) self.assertIn('invalid', response.content)
...@@ -556,7 +590,7 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase): ...@@ -556,7 +590,7 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
preview mode. Either the certificate is marked active or not. preview mode. Either the certificate is marked active or not.
""" """
self.cert.delete() self.cert.delete()
self.assertEqual(len(GeneratedCertificate.objects.all()), 0) self.assertEqual(len(GeneratedCertificate.eligible_certificates.all()), 0)
self._add_course_certificates(count=1, signatory_count=2) self._add_course_certificates(count=1, signatory_count=2)
test_url = get_certificate_url( test_url = get_certificate_url(
user_id=self.user.id, user_id=self.user.id,
......
...@@ -342,7 +342,7 @@ def _get_user_certificate(request, user, course_key, course, preview_mode=None): ...@@ -342,7 +342,7 @@ def _get_user_certificate(request, user, course_key, course, preview_mode=None):
else: else:
# certificate is being viewed by learner or public # certificate is being viewed by learner or public
try: try:
user_certificate = GeneratedCertificate.objects.get( user_certificate = GeneratedCertificate.eligible_certificates.get(
user=user, user=user,
course_id=course_key, course_id=course_key,
status=CertificateStatuses.downloadable status=CertificateStatuses.downloadable
...@@ -459,7 +459,7 @@ def render_cert_by_uuid(request, certificate_uuid): ...@@ -459,7 +459,7 @@ def render_cert_by_uuid(request, certificate_uuid):
This public view generates an HTML representation of the specified certificate This public view generates an HTML representation of the specified certificate
""" """
try: try:
certificate = GeneratedCertificate.objects.get( certificate = GeneratedCertificate.eligible_certificates.get(
verify_uuid=certificate_uuid, verify_uuid=certificate_uuid,
status=CertificateStatuses.downloadable status=CertificateStatuses.downloadable
) )
......
...@@ -75,7 +75,7 @@ def update_certificate(request): ...@@ -75,7 +75,7 @@ def update_certificate(request):
try: try:
course_key = SlashSeparatedCourseKey.from_deprecated_string(xqueue_body['course_id']) course_key = SlashSeparatedCourseKey.from_deprecated_string(xqueue_body['course_id'])
cert = GeneratedCertificate.objects.get( cert = GeneratedCertificate.eligible_certificates.get(
user__username=xqueue_body['username'], user__username=xqueue_body['username'],
course_id=course_key, course_id=course_key,
key=xqueue_header['lms_key']) key=xqueue_header['lms_key'])
......
...@@ -619,7 +619,7 @@ class CertificateExceptionViewInstructorApiTest(SharedModuleStoreTestCase): ...@@ -619,7 +619,7 @@ class CertificateExceptionViewInstructorApiTest(SharedModuleStoreTestCase):
# Verify that certificate exception successfully removed from CertificateWhitelist and GeneratedCertificate # Verify that certificate exception successfully removed from CertificateWhitelist and GeneratedCertificate
with self.assertRaises(ObjectDoesNotExist): with self.assertRaises(ObjectDoesNotExist):
CertificateWhitelist.objects.get(user=self.user2, course_id=self.course.id) CertificateWhitelist.objects.get(user=self.user2, course_id=self.course.id)
GeneratedCertificate.objects.get( GeneratedCertificate.eligible_certificates.get(
user=self.user2, course_id=self.course.id, status__not=CertificateStatuses.unavailable user=self.user2, course_id=self.course.id, status__not=CertificateStatuses.unavailable
) )
...@@ -1010,7 +1010,7 @@ class CertificateInvalidationViewTests(SharedModuleStoreTestCase): ...@@ -1010,7 +1010,7 @@ class CertificateInvalidationViewTests(SharedModuleStoreTestCase):
self.fail("The certificate is not invalidated.") self.fail("The certificate is not invalidated.")
# Validate generated certificate was invalidated # Validate generated certificate was invalidated
generated_certificate = GeneratedCertificate.objects.get( generated_certificate = GeneratedCertificate.eligible_certificates.get(
user=self.enrolled_user_1, user=self.enrolled_user_1,
course_id=self.course.id, course_id=self.course.id,
) )
......
...@@ -2875,7 +2875,7 @@ def add_certificate_exception(course_key, student, certificate_exception): ...@@ -2875,7 +2875,7 @@ def add_certificate_exception(course_key, student, certificate_exception):
} }
) )
generated_certificate = GeneratedCertificate.objects.filter( generated_certificate = GeneratedCertificate.eligible_certificates.filter(
user=student, user=student,
course_id=course_key, course_id=course_key,
status=CertificateStatuses.downloadable, status=CertificateStatuses.downloadable,
...@@ -2912,7 +2912,10 @@ def remove_certificate_exception(course_key, student): ...@@ -2912,7 +2912,10 @@ def remove_certificate_exception(course_key, student):
) )
try: try:
generated_certificate = GeneratedCertificate.objects.get(user=student, course_id=course_key) generated_certificate = GeneratedCertificate.objects.get( # pylint: disable=no-member
user=student,
course_id=course_key
)
generated_certificate.invalidate() generated_certificate.invalidate()
except ObjectDoesNotExist: except ObjectDoesNotExist:
# Certificate has not been generated yet, so just remove the certificate exception from white list # Certificate has not been generated yet, so just remove the certificate exception from white list
......
...@@ -185,7 +185,7 @@ def issued_certificates(course_key, features): ...@@ -185,7 +185,7 @@ def issued_certificates(course_key, features):
report_run_date = datetime.date.today().strftime("%B %d, %Y") report_run_date = datetime.date.today().strftime("%B %d, %Y")
certificate_features = [x for x in CERTIFICATE_FEATURES if x in features] certificate_features = [x for x in CERTIFICATE_FEATURES if x in features]
generated_certificates = list(GeneratedCertificate.objects.filter( generated_certificates = list(GeneratedCertificate.eligible_certificates.filter(
course_id=course_key, course_id=course_key,
status=CertificateStatuses.downloadable status=CertificateStatuses.downloadable
).values(*certificate_features).annotate(total_issued_certificate=Count('mode'))) ).values(*certificate_features).annotate(total_issued_certificate=Count('mode')))
......
...@@ -1584,7 +1584,7 @@ def invalidate_generated_certificates(course_id, enrolled_students, certificate_ ...@@ -1584,7 +1584,7 @@ def invalidate_generated_certificates(course_id, enrolled_students, certificate_
:param enrolled_students: (queryset or list) students enrolled in the course :param enrolled_students: (queryset or list) students enrolled in the course
:param certificate_statuses: certificates statuses for whom to remove generated certificate :param certificate_statuses: certificates statuses for whom to remove generated certificate
""" """
certificates = GeneratedCertificate.objects.filter( certificates = GeneratedCertificate.objects.filter( # pylint: disable=no-member
user__in=enrolled_students, user__in=enrolled_students,
course_id=course_id, course_id=course_id,
status__in=certificate_statuses, status__in=certificate_statuses,
......
...@@ -1802,7 +1802,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase): ...@@ -1802,7 +1802,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
}, },
result result
) )
generated_certificates = GeneratedCertificate.objects.filter( generated_certificates = GeneratedCertificate.eligible_certificates.filter(
user__in=students, user__in=students,
course_id=self.course.id, course_id=self.course.id,
mode='honor' mode='honor'
...@@ -1912,7 +1912,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase): ...@@ -1912,7 +1912,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
result result
) )
generated_certificates = GeneratedCertificate.objects.filter( generated_certificates = GeneratedCertificate.eligible_certificates.filter(
user__in=students, user__in=students,
course_id=self.course.id, course_id=self.course.id,
mode='honor' mode='honor'
......
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