Merge pull request #11151 from edx/peter-fogg/disable-audit-cert-gen
[wip] Mark GeneratedCertificate records for audit mode as not eligible for a certificate.
This commit is contained in:
@@ -592,6 +592,18 @@ class CourseMode(models.Model):
|
||||
modes = cls.modes_for_course(course_id)
|
||||
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' and
|
||||
eligible_for_certificate=True.
|
||||
"""
|
||||
return mode_slug != cls.AUDIT
|
||||
|
||||
def to_tuple(self):
|
||||
"""
|
||||
Takes a mode model and turns it into a model named tuple.
|
||||
|
||||
@@ -430,3 +430,16 @@ class CourseModeModelTest(TestCase):
|
||||
verified_mode.expiration_datetime = None
|
||||
self.assertFalse(verified_mode.expiration_datetime_is_explicit)
|
||||
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):
|
||||
cert_grades = {
|
||||
cert.user.username: cert.grade
|
||||
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"
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -190,7 +190,7 @@ CREATE TABLE `assessment_assessmentpart` (
|
||||
`feedback` longtext NOT NULL,
|
||||
`assessment_id` int(11) NOT NULL,
|
||||
`criterion_id` int(11) NOT NULL,
|
||||
`option_id` int(11),
|
||||
`option_id` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `asses_assessment_id_1d752290138ce479_fk_assessment_assessment_id` (`assessment_id`),
|
||||
KEY `assessment_assessmentpart_385b00a3` (`criterion_id`),
|
||||
@@ -386,7 +386,7 @@ CREATE TABLE `auth_permission` (
|
||||
PRIMARY KEY (`id`),
|
||||
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`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=710 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=716 DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `auth_registration`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
@@ -418,7 +418,7 @@ CREATE TABLE `auth_user` (
|
||||
`date_joined` datetime(6) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `username` (`username`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `auth_user_groups`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
@@ -588,7 +588,7 @@ CREATE TABLE `bulk_email_courseemailtemplate` (
|
||||
`name` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `name` (`name`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `bulk_email_optout`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
@@ -659,7 +659,7 @@ CREATE TABLE `certificates_badgeimageconfiguration` (
|
||||
`default` tinyint(1) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `mode` (`mode`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `certificates_certificategenerationconfiguration`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
@@ -717,7 +717,25 @@ CREATE TABLE `certificates_certificatehtmlviewconfiguration` (
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `certificates_cert_changed_by_id_1de6cf549bca749b_fk_auth_user_id` (`changed_by_id`),
|
||||
CONSTRAINT `certificates_cert_changed_by_id_1de6cf549bca749b_fk_auth_user_id` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `certificates_certificateinvalidation`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `certificates_certificateinvalidation` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`created` datetime(6) NOT NULL,
|
||||
`modified` datetime(6) NOT NULL,
|
||||
`notes` longtext,
|
||||
`active` tinyint(1) NOT NULL,
|
||||
`generated_certificate_id` int(11) NOT NULL,
|
||||
`invalidated_by_id` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `fa0dc816ca8028cd93e5f2289d405d87` (`generated_certificate_id`),
|
||||
KEY `certificates__invalidated_by_id_5198db337fb56b7b_fk_auth_user_id` (`invalidated_by_id`),
|
||||
CONSTRAINT `certificates__invalidated_by_id_5198db337fb56b7b_fk_auth_user_id` FOREIGN KEY (`invalidated_by_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `fa0dc816ca8028cd93e5f2289d405d87` FOREIGN KEY (`generated_certificate_id`) REFERENCES `certificates_generatedcertificate` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `certificates_certificatetemplate`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
@@ -748,7 +766,7 @@ CREATE TABLE `certificates_certificatetemplateasset` (
|
||||
`modified` datetime(6) NOT NULL,
|
||||
`description` varchar(255) DEFAULT NULL,
|
||||
`asset` varchar(255) NOT NULL,
|
||||
`asset_slug` varchar(255),
|
||||
`asset_slug` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `asset_slug` (`asset_slug`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
@@ -822,6 +840,7 @@ CREATE TABLE `certificates_generatedcertificate` (
|
||||
`modified_date` datetime(6) NOT NULL,
|
||||
`error_reason` varchar(512) NOT NULL,
|
||||
`user_id` int(11) NOT NULL,
|
||||
`eligible_for_certificate` tinyint(1) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `certificates_generatedcertificate_user_id_552a0fa6f7d3f7e8_uniq` (`user_id`,`course_id`),
|
||||
KEY `certificates_generatedcertific_verify_uuid_1b5a14bb83c471ff_uniq` (`verify_uuid`),
|
||||
@@ -1089,7 +1108,7 @@ CREATE TABLE `course_overviews_courseoverview` (
|
||||
`enrollment_domain` longtext,
|
||||
`invitation_only` tinyint(1) NOT NULL,
|
||||
`max_student_enrollments_allowed` int(11) DEFAULT NULL,
|
||||
`announcement` datetime(6),
|
||||
`announcement` datetime(6) DEFAULT NULL,
|
||||
`catalog_visibility` longtext,
|
||||
`course_video_url` longtext,
|
||||
`effort` longtext,
|
||||
@@ -1479,7 +1498,7 @@ CREATE TABLE `dark_lang_darklangconfig` (
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `dark_lang_darklan_changed_by_id_7e1defb1121d58b8_fk_auth_user_id` (`changed_by_id`),
|
||||
CONSTRAINT `dark_lang_darklan_changed_by_id_7e1defb1121d58b8_fk_auth_user_id` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `django_admin_log`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
@@ -1556,7 +1575,7 @@ CREATE TABLE `django_content_type` (
|
||||
`model` varchar(100) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `django_content_type_app_label_45f3b1d93ec8c61c_uniq` (`app_label`,`model`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=236 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=238 DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `django_migrations`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
@@ -1567,7 +1586,7 @@ CREATE TABLE `django_migrations` (
|
||||
`name` varchar(255) NOT NULL,
|
||||
`applied` datetime(6) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=97 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `django_openid_auth_association`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
@@ -1769,7 +1788,7 @@ CREATE TABLE `edxval_profile` (
|
||||
`profile_name` varchar(50) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `profile_name` (`profile_name`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `edxval_subtitle`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
@@ -1813,7 +1832,7 @@ CREATE TABLE `embargo_country` (
|
||||
`country` varchar(2) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `country` (`country`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=250 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `embargo_countryaccessrule`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
@@ -2033,7 +2052,7 @@ CREATE TABLE `milestones_milestonerelationshiptype` (
|
||||
`active` tinyint(1) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `name` (`name`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `milestones_usermilestone`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
@@ -2104,7 +2123,7 @@ CREATE TABLE `notify_notification` (
|
||||
`is_viewed` tinyint(1) NOT NULL,
|
||||
`is_emailed` tinyint(1) NOT NULL,
|
||||
`created` datetime(6) NOT NULL,
|
||||
`subscription_id` int(11),
|
||||
`subscription_id` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `notify_notification_ef42673f` (`subscription_id`),
|
||||
CONSTRAINT `D48032390695e0699e92b8d7ccdbff7e` FOREIGN KEY (`subscription_id`) REFERENCES `notify_subscription` (`subscription_id`)
|
||||
@@ -2679,9 +2698,9 @@ CREATE TABLE `shoppingcart_courseregistrationcode` (
|
||||
`mode_slug` varchar(100) DEFAULT NULL,
|
||||
`is_valid` tinyint(1) NOT NULL,
|
||||
`created_by_id` int(11) NOT NULL,
|
||||
`invoice_id` int(11),
|
||||
`order_id` int(11),
|
||||
`invoice_item_id` int(11),
|
||||
`invoice_id` int(11) DEFAULT NULL,
|
||||
`order_id` int(11) DEFAULT NULL,
|
||||
`invoice_item_id` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `code` (`code`),
|
||||
KEY `shoppingcart_cour_created_by_id_11125a9667aa01c9_fk_auth_user_id` (`created_by_id`),
|
||||
@@ -2995,6 +3014,20 @@ CREATE TABLE `splash_splashconfig` (
|
||||
CONSTRAINT `splash_splashconf_changed_by_id_735b38ad8ed19270_fk_auth_user_id` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `static_replace_assetbaseurlconfig`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `static_replace_assetbaseurlconfig` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`change_date` datetime(6) NOT NULL,
|
||||
`changed_by_id` int(11) DEFAULT NULL,
|
||||
`enabled` tinyint(1) NOT NULL,
|
||||
`base_url` longtext NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `static_replace_as_changed_by_id_796c2e5b1bee7027_fk_auth_user_id` (`changed_by_id`),
|
||||
CONSTRAINT `static_replace_as_changed_by_id_796c2e5b1bee7027_fk_auth_user_id` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `status_coursemessage`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
@@ -3336,7 +3369,7 @@ CREATE TABLE `submissions_score` (
|
||||
`created_at` datetime(6) NOT NULL,
|
||||
`reset` tinyint(1) NOT NULL,
|
||||
`student_item_id` int(11) NOT NULL,
|
||||
`submission_id` int(11),
|
||||
`submission_id` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `submissions_score_fde81f11` (`created_at`),
|
||||
KEY `submissions_score_02d5e83e` (`student_item_id`),
|
||||
@@ -3697,7 +3730,7 @@ CREATE TABLE `util_ratelimitconfiguration` (
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `util_ratelimitcon_changed_by_id_2c8891cb4854f3b5_fk_auth_user_id` (`changed_by_id`),
|
||||
CONSTRAINT `util_ratelimitcon_changed_by_id_2c8891cb4854f3b5_fk_auth_user_id` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `verify_student_historicalverificationdeadline`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
@@ -3870,9 +3903,9 @@ CREATE TABLE `wiki_article` (
|
||||
`group_write` tinyint(1) NOT NULL,
|
||||
`other_read` tinyint(1) NOT NULL,
|
||||
`other_write` tinyint(1) NOT NULL,
|
||||
`current_revision_id` int(11),
|
||||
`group_id` int(11),
|
||||
`owner_id` int(11),
|
||||
`current_revision_id` int(11) DEFAULT NULL,
|
||||
`group_id` int(11) DEFAULT NULL,
|
||||
`owner_id` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `current_revision_id` (`current_revision_id`),
|
||||
KEY `wiki_article_0e939a4f` (`group_id`),
|
||||
@@ -3944,7 +3977,7 @@ DROP TABLE IF EXISTS `wiki_attachment`;
|
||||
CREATE TABLE `wiki_attachment` (
|
||||
`reusableplugin_ptr_id` int(11) NOT NULL,
|
||||
`original_filename` varchar(256) DEFAULT NULL,
|
||||
`current_revision_id` int(11),
|
||||
`current_revision_id` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`reusableplugin_ptr_id`),
|
||||
UNIQUE KEY `current_revision_id` (`current_revision_id`),
|
||||
CONSTRAINT `D32d32ecb0471dc863a4e19562842024` FOREIGN KEY (`current_revision_id`) REFERENCES `wiki_attachmentrevision` (`id`),
|
||||
@@ -3967,8 +4000,8 @@ CREATE TABLE `wiki_attachmentrevision` (
|
||||
`file` varchar(100) NOT NULL,
|
||||
`description` longtext NOT NULL,
|
||||
`attachment_id` int(11) NOT NULL,
|
||||
`previous_revision_id` int(11),
|
||||
`user_id` int(11),
|
||||
`previous_revision_id` int(11) DEFAULT NULL,
|
||||
`user_id` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `wiki_attachmentrevision_07ba63f5` (`attachment_id`),
|
||||
KEY `wiki_attachmentrevision_e8680b8a` (`previous_revision_id`),
|
||||
@@ -4027,7 +4060,7 @@ DROP TABLE IF EXISTS `wiki_revisionplugin`;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `wiki_revisionplugin` (
|
||||
`articleplugin_ptr_id` int(11) NOT NULL,
|
||||
`current_revision_id` int(11),
|
||||
`current_revision_id` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`articleplugin_ptr_id`),
|
||||
UNIQUE KEY `current_revision_id` (`current_revision_id`),
|
||||
CONSTRAINT `D03d76148e98b4bc99e3137189894366` FOREIGN KEY (`current_revision_id`) REFERENCES `wiki_revisionpluginrevision` (`id`),
|
||||
@@ -4048,8 +4081,8 @@ CREATE TABLE `wiki_revisionpluginrevision` (
|
||||
`deleted` tinyint(1) NOT NULL,
|
||||
`locked` tinyint(1) NOT NULL,
|
||||
`plugin_id` int(11) NOT NULL,
|
||||
`previous_revision_id` int(11),
|
||||
`user_id` int(11),
|
||||
`previous_revision_id` int(11) DEFAULT NULL,
|
||||
`user_id` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `wiki_revisionpluginrevision_b25eaab4` (`plugin_id`),
|
||||
KEY `wiki_revisionpluginrevision_e8680b8a` (`previous_revision_id`),
|
||||
|
||||
@@ -78,7 +78,7 @@ def get_certificates_for_user(username):
|
||||
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,
|
||||
if insecure:
|
||||
xqueue.use_https = False
|
||||
generate_pdf = not has_html_certificates_enabled(course_key, course)
|
||||
status, cert = xqueue.add_cert(student, course_key,
|
||||
course=course,
|
||||
generate_pdf=generate_pdf,
|
||||
forced_grade=forced_grade)
|
||||
if status in [CertificateStatuses.generating, CertificateStatuses.downloadable]:
|
||||
cert = xqueue.add_cert(
|
||||
student,
|
||||
course_key,
|
||||
course=course,
|
||||
generate_pdf=generate_pdf,
|
||||
forced_grade=forced_grade
|
||||
)
|
||||
if cert.status in [CertificateStatuses.generating, CertificateStatuses.downloadable]:
|
||||
emit_certificate_event('created', student, course_key, course, {
|
||||
'user_id': student.id,
|
||||
'course_id': unicode(course_key),
|
||||
@@ -121,7 +124,7 @@ def generate_user_certificates(student, course_key, course=None, insecure=False,
|
||||
'enrollment_mode': cert.mode,
|
||||
'generation_mode': generation_mode
|
||||
})
|
||||
return status
|
||||
return cert.status
|
||||
|
||||
|
||||
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):
|
||||
)
|
||||
return url
|
||||
try:
|
||||
user_certificate = GeneratedCertificate.objects.get(
|
||||
user_certificate = GeneratedCertificate.eligible_certificates.get(
|
||||
user=user_id,
|
||||
course_id=course_id
|
||||
)
|
||||
|
||||
@@ -76,7 +76,7 @@ class Command(BaseCommand):
|
||||
status = options.get('status', CertificateStatuses.downloadable)
|
||||
grade = options.get('grade', '')
|
||||
|
||||
cert, created = GeneratedCertificate.objects.get_or_create(
|
||||
cert, created = GeneratedCertificate.eligible_certificates.get_or_create(
|
||||
user=user,
|
||||
course_id=course_key
|
||||
)
|
||||
|
||||
@@ -42,8 +42,9 @@ class Command(BaseCommand):
|
||||
def handle(self, *args, **options):
|
||||
course_id = options['course']
|
||||
print "Fetching ungraded students for {0}".format(course_id)
|
||||
ungraded = GeneratedCertificate.objects.filter(
|
||||
course_id__exact=course_id).filter(grade__exact='')
|
||||
ungraded = GeneratedCertificate.objects.filter( # pylint: disable=no-member
|
||||
course_id__exact=course_id
|
||||
).filter(grade__exact='')
|
||||
course = courses.get_course_by_id(course_id)
|
||||
factory = RequestFactory()
|
||||
request = factory.get('/')
|
||||
|
||||
@@ -70,14 +70,17 @@ class Command(BaseCommand):
|
||||
enrolled_total = User.objects.filter(
|
||||
courseenrollment__course_id=course_id
|
||||
)
|
||||
verified_enrolled = GeneratedCertificate.objects.filter(
|
||||
course_id__exact=course_id, mode__exact='verified'
|
||||
verified_enrolled = GeneratedCertificate.objects.filter( # pylint: disable=no-member
|
||||
course_id__exact=course_id,
|
||||
mode__exact='verified'
|
||||
)
|
||||
honor_enrolled = GeneratedCertificate.objects.filter(
|
||||
course_id__exact=course_id, mode__exact='honor'
|
||||
honor_enrolled = GeneratedCertificate.objects.filter( # pylint: disable=no-member
|
||||
course_id__exact=course_id,
|
||||
mode__exact='honor'
|
||||
)
|
||||
audit_enrolled = GeneratedCertificate.objects.filter(
|
||||
course_id__exact=course_id, mode__exact='audit'
|
||||
audit_enrolled = GeneratedCertificate.objects.filter( # pylint: disable=no-member
|
||||
course_id__exact=course_id,
|
||||
mode__exact='audit'
|
||||
)
|
||||
|
||||
cert_data[course_id] = {
|
||||
@@ -88,7 +91,7 @@ class Command(BaseCommand):
|
||||
'audit_enrolled': audit_enrolled.count()
|
||||
}
|
||||
|
||||
status_tally = GeneratedCertificate.objects.filter(
|
||||
status_tally = GeneratedCertificate.objects.filter( # pylint: disable=no-member
|
||||
course_id__exact=course_id
|
||||
).values('status').annotate(
|
||||
dcount=Count('status')
|
||||
@@ -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,
|
||||
status__exact='downloadable'
|
||||
).values('mode').annotate(
|
||||
|
||||
@@ -81,7 +81,7 @@ class Command(BaseCommand):
|
||||
# Retrieve the IDs of generated certificates with
|
||||
# error status in the set of courses we're considering.
|
||||
queryset = (
|
||||
GeneratedCertificate.objects.select_related('user')
|
||||
GeneratedCertificate.objects.select_related('user') # pylint: disable=no-member
|
||||
).filter(status=CertificateStatuses.error)
|
||||
if only_course_keys:
|
||||
queryset = queryset.filter(course_id__in=only_course_keys)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('certificates', '0007_certificateinvalidation'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='generatedcertificate',
|
||||
name='eligible_for_certificate',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
||||
@@ -143,7 +143,7 @@ class CertificateWhitelist(models.Model):
|
||||
if student:
|
||||
white_list = white_list.filter(user=student)
|
||||
result = []
|
||||
generated_certificates = GeneratedCertificate.objects.filter(
|
||||
generated_certificates = GeneratedCertificate.eligible_certificates.filter(
|
||||
course_id=course_id,
|
||||
user__in=[exception.user for exception in white_list],
|
||||
status=CertificateStatuses.downloadable
|
||||
@@ -168,11 +168,40 @@ class CertificateWhitelist(models.Model):
|
||||
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().filter(eligible_for_certificate=True)
|
||||
|
||||
|
||||
class GeneratedCertificate(models.Model):
|
||||
"""
|
||||
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')
|
||||
|
||||
VERIFIED_CERTS_MODES = [CourseMode.VERIFIED, CourseMode.CREDIT_MODE]
|
||||
@@ -191,6 +220,19 @@ class GeneratedCertificate(models.Model):
|
||||
created_date = models.DateTimeField(auto_now_add=True)
|
||||
modified_date = models.DateTimeField(auto_now=True)
|
||||
error_reason = models.CharField(max_length=512, blank=True, default='')
|
||||
# Whether or not this GeneratedCertificate represents a
|
||||
# certificate which can be shown to the user. Grading and
|
||||
# certificate logic is intertwined here, so even enrollments
|
||||
# without certificates (as of Jan 2016, this is only audit mode)
|
||||
# create a GeneratedCertificate record to record the learner's
|
||||
# final grade. Since audit enrollments used to have certificates
|
||||
# and now do not, we need to be able to distinguish between old
|
||||
# records and new in our analytics and reporting. The way we'll do
|
||||
# this is by checking this field. By default it is True in order
|
||||
# to make sure old records are counted correctly, and in
|
||||
# `GeneratedCertificate.add_cert` we set it to False for new audit
|
||||
# enrollments.
|
||||
eligible_for_certificate = models.BooleanField(default=True)
|
||||
|
||||
class Meta(object):
|
||||
unique_together = (('user', 'course_id'),)
|
||||
@@ -410,7 +452,7 @@ def certificate_status_for_student(student, course_id):
|
||||
'''
|
||||
|
||||
try:
|
||||
generated_certificate = GeneratedCertificate.objects.get(
|
||||
generated_certificate = GeneratedCertificate.objects.get( # pylint: disable=no-member
|
||||
user=student, course_id=course_id)
|
||||
cert_status = {
|
||||
'status': generated_certificate.status,
|
||||
|
||||
@@ -20,6 +20,7 @@ from student.models import UserProfile, CourseEnrollment
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
|
||||
from certificates.models import (
|
||||
CertificateStatuses,
|
||||
GeneratedCertificate,
|
||||
certificate_status_for_student,
|
||||
CertificateStatuses as status,
|
||||
@@ -120,14 +121,14 @@ class XQueueCertInterface(object):
|
||||
Change the certificate status to unavailable (if it exists) and request
|
||||
grading. Passing grades will put a certificate request on the queue.
|
||||
|
||||
Return the status object.
|
||||
Return the certificate.
|
||||
"""
|
||||
# TODO: when del_cert is implemented and plumbed through certificates
|
||||
# repo also, do a deletion followed by a creation r/t a simple
|
||||
# recreation. XXX: this leaves orphan cert files laying around in
|
||||
# AWS. See note in the docstring too.
|
||||
try:
|
||||
certificate = GeneratedCertificate.objects.get(user=student, course_id=course_id)
|
||||
certificate = GeneratedCertificate.eligible_certificates.get(user=student, course_id=course_id)
|
||||
|
||||
LOGGER.info(
|
||||
(
|
||||
@@ -183,8 +184,7 @@ class XQueueCertInterface(object):
|
||||
raise NotImplementedError
|
||||
|
||||
# pylint: disable=too-many-statements
|
||||
def add_cert(self, student, course_id, course=None, forced_grade=None, template_file=None,
|
||||
title='None', generate_pdf=True):
|
||||
def add_cert(self, student, course_id, course=None, forced_grade=None, template_file=None, generate_pdf=True):
|
||||
"""
|
||||
Request a new certificate for a student.
|
||||
|
||||
@@ -211,7 +211,7 @@ class XQueueCertInterface(object):
|
||||
If a student does not have a passing grade the status
|
||||
will change to status.notpassing
|
||||
|
||||
Returns the student's status and newly created certificate instance
|
||||
Returns the newly created certificate instance
|
||||
"""
|
||||
|
||||
valid_statuses = [
|
||||
@@ -224,7 +224,6 @@ class XQueueCertInterface(object):
|
||||
]
|
||||
|
||||
cert_status = certificate_status_for_student(student, course_id)['status']
|
||||
new_status = cert_status
|
||||
cert = None
|
||||
|
||||
if cert_status not in valid_statuses:
|
||||
@@ -239,155 +238,98 @@ class XQueueCertInterface(object):
|
||||
cert_status,
|
||||
unicode(valid_statuses)
|
||||
)
|
||||
return None
|
||||
|
||||
# The caller can optionally pass a course in to avoid
|
||||
# re-fetching it from Mongo. If they have not provided one,
|
||||
# get it from the modulestore.
|
||||
if course is None:
|
||||
course = modulestore().get_course(course_id, depth=0)
|
||||
|
||||
profile = UserProfile.objects.get(user=student)
|
||||
profile_name = profile.name
|
||||
|
||||
# Needed for access control in grading.
|
||||
self.request.user = student
|
||||
self.request.session = {}
|
||||
|
||||
is_whitelisted = self.whitelist.filter(user=student, course_id=course_id, whitelist=True).exists()
|
||||
grade = grades.grade(student, self.request, course)
|
||||
enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(student, course_id)
|
||||
mode_is_verified = enrollment_mode in GeneratedCertificate.VERIFIED_CERTS_MODES
|
||||
user_is_verified = SoftwareSecurePhotoVerification.user_is_verified(student)
|
||||
cert_mode = enrollment_mode
|
||||
is_eligible_for_certificate = is_whitelisted or CourseMode.is_eligible_for_certificate(enrollment_mode)
|
||||
|
||||
# For credit mode generate verified certificate
|
||||
if cert_mode == CourseMode.CREDIT_MODE:
|
||||
cert_mode = CourseMode.VERIFIED
|
||||
|
||||
if template_file is not None:
|
||||
template_pdf = template_file
|
||||
elif mode_is_verified and user_is_verified:
|
||||
template_pdf = "certificate-template-{id.org}-{id.course}-verified.pdf".format(id=course_id)
|
||||
elif mode_is_verified and not user_is_verified:
|
||||
template_pdf = "certificate-template-{id.org}-{id.course}.pdf".format(id=course_id)
|
||||
cert_mode = GeneratedCertificate.MODES.honor
|
||||
else:
|
||||
# grade the student
|
||||
# honor code and audit students
|
||||
template_pdf = "certificate-template-{id.org}-{id.course}.pdf".format(id=course_id)
|
||||
if forced_grade:
|
||||
grade['grade'] = forced_grade
|
||||
|
||||
# re-use the course passed in optionally so we don't have to re-fetch everything
|
||||
# for every student
|
||||
if course is None:
|
||||
course = modulestore().get_course(course_id, depth=0)
|
||||
profile = UserProfile.objects.get(user=student)
|
||||
profile_name = profile.name
|
||||
cert, __ = GeneratedCertificate.eligible_certificates.get_or_create(user=student, course_id=course_id)
|
||||
|
||||
# Needed
|
||||
self.request.user = student
|
||||
self.request.session = {}
|
||||
cert.mode = cert_mode
|
||||
cert.user = student
|
||||
cert.grade = grade['percent']
|
||||
cert.course_id = course_id
|
||||
cert.name = profile_name
|
||||
cert.download_url = ''
|
||||
|
||||
course_name = course.display_name or unicode(course_id)
|
||||
is_whitelisted = self.whitelist.filter(user=student, course_id=course_id, whitelist=True).exists()
|
||||
grade = grades.grade(student, self.request, course)
|
||||
enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(student, course_id)
|
||||
mode_is_verified = enrollment_mode in GeneratedCertificate.VERIFIED_CERTS_MODES
|
||||
user_is_verified = SoftwareSecurePhotoVerification.user_is_verified(student)
|
||||
cert_mode = enrollment_mode
|
||||
# If this user's enrollment is not eligible to receive a
|
||||
# certificate, mark it as such for reporting and
|
||||
# analytics.
|
||||
if not is_eligible_for_certificate:
|
||||
cert.eligible_for_certificate = False
|
||||
cert.status = CertificateStatuses.auditing
|
||||
cert.save()
|
||||
LOGGER.info(
|
||||
u"Student %s with enrollment mode %s is not eligible for a certificate.",
|
||||
student.id,
|
||||
enrollment_mode
|
||||
)
|
||||
return cert
|
||||
|
||||
# For credit mode generate verified certificate
|
||||
if cert_mode == CourseMode.CREDIT_MODE:
|
||||
cert_mode = CourseMode.VERIFIED
|
||||
# Strip HTML from grade range label
|
||||
grade_contents = grade.get('grade', None)
|
||||
try:
|
||||
grade_contents = lxml.html.fromstring(grade_contents).text_content()
|
||||
except (TypeError, XMLSyntaxError, ParserError) as exc:
|
||||
LOGGER.info(
|
||||
(
|
||||
u"Could not retrieve grade for student %s "
|
||||
u"in the course '%s' "
|
||||
u"because an exception occurred while parsing the "
|
||||
u"grade contents '%s' as HTML. "
|
||||
u"The exception was: '%s'"
|
||||
),
|
||||
student.id,
|
||||
unicode(course_id),
|
||||
grade_contents,
|
||||
unicode(exc)
|
||||
)
|
||||
|
||||
if mode_is_verified and user_is_verified:
|
||||
template_pdf = "certificate-template-{id.org}-{id.course}-verified.pdf".format(id=course_id)
|
||||
elif mode_is_verified and not user_is_verified:
|
||||
template_pdf = "certificate-template-{id.org}-{id.course}.pdf".format(id=course_id)
|
||||
cert_mode = GeneratedCertificate.MODES.honor
|
||||
else:
|
||||
# honor code and audit students
|
||||
template_pdf = "certificate-template-{id.org}-{id.course}.pdf".format(id=course_id)
|
||||
if forced_grade:
|
||||
grade['grade'] = forced_grade
|
||||
|
||||
cert, __ = GeneratedCertificate.objects.get_or_create(user=student, course_id=course_id)
|
||||
|
||||
cert.mode = cert_mode
|
||||
cert.user = student
|
||||
cert.grade = grade['percent']
|
||||
cert.course_id = course_id
|
||||
cert.name = profile_name
|
||||
cert.download_url = ''
|
||||
# Strip HTML from grade range label
|
||||
grade_contents = grade.get('grade', None)
|
||||
try:
|
||||
grade_contents = lxml.html.fromstring(grade_contents).text_content()
|
||||
except (TypeError, XMLSyntaxError, ParserError) as exc:
|
||||
# Log if the student is whitelisted
|
||||
if is_whitelisted:
|
||||
LOGGER.info(
|
||||
(
|
||||
u"Could not retrieve grade for student %s "
|
||||
u"in the course '%s' "
|
||||
u"because an exception occurred while parsing the "
|
||||
u"grade contents '%s' as HTML. "
|
||||
u"The exception was: '%s'"
|
||||
),
|
||||
u"Student %s is whitelisted in '%s'",
|
||||
student.id,
|
||||
unicode(course_id),
|
||||
grade_contents,
|
||||
unicode(exc)
|
||||
unicode(course_id)
|
||||
)
|
||||
|
||||
# Despite blowing up the xml parser, bad values here are fine
|
||||
grade_contents = None
|
||||
|
||||
if is_whitelisted or grade_contents is not None:
|
||||
|
||||
if is_whitelisted:
|
||||
LOGGER.info(
|
||||
u"Student %s is whitelisted in '%s'",
|
||||
student.id,
|
||||
unicode(course_id)
|
||||
)
|
||||
|
||||
# check to see whether the student is on the
|
||||
# the embargoed country restricted list
|
||||
# otherwise, put a new certificate request
|
||||
# on the queue
|
||||
|
||||
if self.restricted.filter(user=student).exists():
|
||||
new_status = status.restricted
|
||||
cert.status = new_status
|
||||
cert.save()
|
||||
|
||||
LOGGER.info(
|
||||
(
|
||||
u"Student %s is in the embargoed country restricted "
|
||||
u"list, so their certificate status has been set to '%s' "
|
||||
u"for the course '%s'. "
|
||||
u"No certificate generation task was sent to the XQueue."
|
||||
),
|
||||
student.id,
|
||||
new_status,
|
||||
unicode(course_id)
|
||||
)
|
||||
else:
|
||||
key = make_hashkey(random.random())
|
||||
cert.key = key
|
||||
contents = {
|
||||
'action': 'create',
|
||||
'username': student.username,
|
||||
'course_id': unicode(course_id),
|
||||
'course_name': course_name,
|
||||
'name': profile_name,
|
||||
'grade': grade_contents,
|
||||
'template_pdf': template_pdf,
|
||||
}
|
||||
if template_file:
|
||||
contents['template_pdf'] = template_file
|
||||
if generate_pdf:
|
||||
new_status = status.generating
|
||||
else:
|
||||
new_status = status.downloadable
|
||||
cert.verify_uuid = uuid4().hex
|
||||
|
||||
cert.status = new_status
|
||||
cert.save()
|
||||
|
||||
if generate_pdf:
|
||||
try:
|
||||
self._send_to_xqueue(contents, key)
|
||||
except XQueueAddToQueueError as exc:
|
||||
new_status = ExampleCertificate.STATUS_ERROR
|
||||
cert.status = new_status
|
||||
cert.error_reason = unicode(exc)
|
||||
cert.save()
|
||||
LOGGER.critical(
|
||||
(
|
||||
u"Could not add certificate task to XQueue. "
|
||||
u"The course was '%s' and the student was '%s'."
|
||||
u"The certificate task status has been marked as 'error' "
|
||||
u"and can be re-submitted with a management command."
|
||||
), course_id, student.id
|
||||
)
|
||||
else:
|
||||
LOGGER.info(
|
||||
(
|
||||
u"The certificate status has been set to '%s'. "
|
||||
u"Sent a certificate grading task to the XQueue "
|
||||
u"with the key '%s'. "
|
||||
),
|
||||
new_status,
|
||||
key
|
||||
)
|
||||
# If they are not, short-circuit and don't generate cert
|
||||
else:
|
||||
new_status = status.notpassing
|
||||
cert.status = new_status
|
||||
cert.status = status.notpassing
|
||||
cert.save()
|
||||
|
||||
LOGGER.info(
|
||||
@@ -398,10 +340,85 @@ class XQueueCertInterface(object):
|
||||
),
|
||||
student.id,
|
||||
unicode(course_id),
|
||||
new_status
|
||||
cert.status
|
||||
)
|
||||
return cert
|
||||
|
||||
return new_status, cert
|
||||
# Check to see whether the student is on the the embargoed
|
||||
# country restricted list. If so, they should not receive a
|
||||
# certificate -- set their status to restricted and log it.
|
||||
if self.restricted.filter(user=student).exists():
|
||||
cert.status = status.restricted
|
||||
cert.save()
|
||||
|
||||
LOGGER.info(
|
||||
(
|
||||
u"Student %s is in the embargoed country restricted "
|
||||
u"list, so their certificate status has been set to '%s' "
|
||||
u"for the course '%s'. "
|
||||
u"No certificate generation task was sent to the XQueue."
|
||||
),
|
||||
student.id,
|
||||
cert.status,
|
||||
unicode(course_id)
|
||||
)
|
||||
return cert
|
||||
|
||||
# Finally, generate the certificate and send it off.
|
||||
return self._generate_cert(cert, course, student, grade_contents, template_pdf, generate_pdf)
|
||||
|
||||
def _generate_cert(self, cert, course, student, grade_contents, template_pdf, generate_pdf):
|
||||
"""
|
||||
Generate a certificate for the student. If `generate_pdf` is True,
|
||||
sends a request to XQueue.
|
||||
"""
|
||||
course_id = unicode(course.id)
|
||||
|
||||
key = make_hashkey(random.random())
|
||||
cert.key = key
|
||||
contents = {
|
||||
'action': 'create',
|
||||
'username': student.username,
|
||||
'course_id': course_id,
|
||||
'course_name': course.display_name or course_id,
|
||||
'name': cert.name,
|
||||
'grade': grade_contents,
|
||||
'template_pdf': template_pdf,
|
||||
}
|
||||
if generate_pdf:
|
||||
cert.status = status.generating
|
||||
else:
|
||||
cert.status = status.downloadable
|
||||
cert.verify_uuid = uuid4().hex
|
||||
|
||||
cert.save()
|
||||
|
||||
if generate_pdf:
|
||||
try:
|
||||
self._send_to_xqueue(contents, key)
|
||||
except XQueueAddToQueueError as exc:
|
||||
cert.status = ExampleCertificate.STATUS_ERROR
|
||||
cert.error_reason = unicode(exc)
|
||||
cert.save()
|
||||
LOGGER.critical(
|
||||
(
|
||||
u"Could not add certificate task to XQueue. "
|
||||
u"The course was '%s' and the student was '%s'."
|
||||
u"The certificate task status has been marked as 'error' "
|
||||
u"and can be re-submitted with a management command."
|
||||
), course_id, student.id
|
||||
)
|
||||
else:
|
||||
LOGGER.info(
|
||||
(
|
||||
u"The certificate status has been set to '%s'. "
|
||||
u"Sent a certificate grading task to the XQueue "
|
||||
u"with the key '%s'. "
|
||||
),
|
||||
cert.status,
|
||||
key
|
||||
)
|
||||
return cert
|
||||
|
||||
def add_example_cert(self, example_cert):
|
||||
"""Add a task to create an example certificate.
|
||||
|
||||
@@ -231,7 +231,7 @@ class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, Modu
|
||||
certs_api.generate_user_certificates(self.student, self.course.id)
|
||||
|
||||
# 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.assert_event_emitted(
|
||||
'edx.certificate.created',
|
||||
@@ -249,7 +249,7 @@ class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, Modu
|
||||
certs_api.generate_user_certificates(self.student, self.course.id)
|
||||
|
||||
# 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.assertIn(self.ERROR_REASON, cert.error_reason)
|
||||
|
||||
@@ -263,7 +263,7 @@ class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, Modu
|
||||
certs_api.generate_user_certificates(self.student, self.course.id)
|
||||
|
||||
# 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)
|
||||
|
||||
@patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': False})
|
||||
|
||||
@@ -6,6 +6,7 @@ from nose.plugins.attrib import attr
|
||||
from django.test.utils import override_settings
|
||||
from mock import patch
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from opaque_keys.edx.locator import CourseLocator
|
||||
from certificates.tests.factories import BadgeAssertionFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
@@ -30,16 +31,17 @@ class CertificateManagementTest(ModuleStoreTestCase):
|
||||
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. """
|
||||
# Enroll the user in the course
|
||||
CourseEnrollmentFactory.create(
|
||||
user=user,
|
||||
course_id=course_key
|
||||
course_id=course_key,
|
||||
mode=mode
|
||||
)
|
||||
|
||||
# Create the certificate
|
||||
GeneratedCertificate.objects.create(
|
||||
GeneratedCertificate.eligible_certificates.create(
|
||||
user=user,
|
||||
course_id=course_key,
|
||||
status=status
|
||||
@@ -52,7 +54,7 @@ class CertificateManagementTest(ModuleStoreTestCase):
|
||||
|
||||
def _assert_cert_status(self, course_key, user, expected_status):
|
||||
"""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)
|
||||
|
||||
|
||||
@@ -61,9 +63,10 @@ class CertificateManagementTest(ModuleStoreTestCase):
|
||||
class ResubmitErrorCertificatesTest(CertificateManagementTest):
|
||||
"""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'
|
||||
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'
|
||||
with check_mongo_calls(1):
|
||||
@@ -198,7 +201,7 @@ class RegenerateCertificatesTest(CertificateManagementTest):
|
||||
username=self.user.email, course=unicode(key), noop=False, insecure=True, template_file=None,
|
||||
grade_value=None
|
||||
)
|
||||
certificate = GeneratedCertificate.objects.get(
|
||||
certificate = GeneratedCertificate.eligible_certificates.get(
|
||||
user=self.user,
|
||||
course_id=key
|
||||
)
|
||||
@@ -236,7 +239,7 @@ class UngenerateCertificatesTest(CertificateManagementTest):
|
||||
course=unicode(key), noop=False, insecure=True, force=False
|
||||
)
|
||||
self.assertTrue(mock_send_to_queue.called)
|
||||
certificate = GeneratedCertificate.objects.get(
|
||||
certificate = GeneratedCertificate.eligible_certificates.get(
|
||||
user=self.user,
|
||||
course_id=key
|
||||
)
|
||||
|
||||
@@ -28,7 +28,7 @@ class CreateFakeCertTest(TestCase):
|
||||
cert_mode='verified',
|
||||
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.mode, 'verified')
|
||||
self.assertEqual(cert.grade, '0.89')
|
||||
@@ -41,7 +41,7 @@ class CreateFakeCertTest(TestCase):
|
||||
unicode(self.COURSE_KEY),
|
||||
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')
|
||||
|
||||
def test_too_few_args(self):
|
||||
|
||||
@@ -8,13 +8,20 @@ from django.test.utils import override_settings
|
||||
from nose.plugins.attrib import attr
|
||||
from path import Path as path
|
||||
|
||||
from opaque_keys.edx.locator import CourseLocator
|
||||
from certificates.models import (
|
||||
ExampleCertificate,
|
||||
ExampleCertificateSet,
|
||||
CertificateHtmlViewConfiguration,
|
||||
CertificateTemplateAsset,
|
||||
BadgeImageConfiguration)
|
||||
BadgeImageConfiguration,
|
||||
EligibleCertificateManager,
|
||||
GeneratedCertificate,
|
||||
)
|
||||
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['CERTS_HTML_VIEW_CONFIG_PATH'] = 'invalid/path/to/config.json'
|
||||
@@ -234,3 +241,42 @@ class CertificateTemplateAssetTest(TestCase):
|
||||
|
||||
certificate_template_asset = CertificateTemplateAsset.objects.get(id=1)
|
||||
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(
|
||||
eligible_for_certificate=True,
|
||||
user=self.user,
|
||||
course_id=self.courses[0].id # pylint: disable=no-member
|
||||
)
|
||||
self.ineligible_cert = GeneratedCertificateFactory.create(
|
||||
eligible_for_certificate=False,
|
||||
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
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from opaque_keys.edx.locator import CourseLocator
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from student.tests.factories import UserFactory, CourseEnrollmentFactory
|
||||
@@ -22,13 +23,14 @@ from xmodule.modulestore.tests.factories import CourseFactory
|
||||
# in our `XQueueCertInterface` implementation.
|
||||
from capa.xqueue_interface import XQueueInterface
|
||||
|
||||
from certificates.queue import XQueueCertInterface
|
||||
from certificates.models import (
|
||||
ExampleCertificateSet,
|
||||
ExampleCertificate,
|
||||
GeneratedCertificate,
|
||||
CertificateStatuses,
|
||||
)
|
||||
from certificates.queue import XQueueCertInterface
|
||||
from certificates.tests.factories import CertificateWhitelistFactory
|
||||
from lms.djangoapps.verify_student.tests.factories import SoftwareSecurePhotoVerificationFactory
|
||||
|
||||
|
||||
@@ -74,7 +76,7 @@ class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase):
|
||||
|
||||
# Verify that add_cert method does not add message to queue
|
||||
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.assertIsNotNone(certificate.verify_uuid)
|
||||
|
||||
@@ -84,7 +86,11 @@ class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase):
|
||||
template_name = 'certificate-template-{id.org}-{id.course}.pdf'.format(
|
||||
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')
|
||||
def test_add_cert_with_verified_certificates(self, mode):
|
||||
@@ -95,10 +101,40 @@ class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase):
|
||||
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 assert_queue_response(self, mode, expected_mode, expected_template_name):
|
||||
"""Dry method for course enrollment and adding request to queue."""
|
||||
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 add_cert_to_queue(self, mode):
|
||||
"""
|
||||
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(
|
||||
user=self.user_2,
|
||||
course_id=self.course.id,
|
||||
@@ -109,19 +145,42 @@ class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase):
|
||||
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)
|
||||
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
|
||||
self.assertTrue(mock_send.called)
|
||||
__, kwargs = mock_send.call_args_list[0]
|
||||
|
||||
actual_header = json.loads(kwargs['header'])
|
||||
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'])
|
||||
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.assertFalse(certificate.eligible_for_certificate)
|
||||
self.assertEqual(certificate.mode, expected_mode)
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
@override_settings(CERT_QUEUE='certificates')
|
||||
|
||||
@@ -71,7 +71,7 @@ class CertificateSupportTestCase(ModuleStoreTestCase):
|
||||
)
|
||||
|
||||
# Create certificates for the student
|
||||
self.cert = GeneratedCertificate.objects.create(
|
||||
self.cert = GeneratedCertificate.eligible_certificates.create(
|
||||
user=self.student,
|
||||
course_id=self.CERT_COURSE_KEY,
|
||||
grade=self.CERT_GRADE,
|
||||
@@ -259,7 +259,7 @@ class CertificateRegenerateTests(CertificateSupportTestCase):
|
||||
# Check that the user's certificate was updated
|
||||
# Since the student hasn't actually passed the course,
|
||||
# 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)
|
||||
|
||||
def test_regenerate_certificate_missing_params(self):
|
||||
@@ -298,7 +298,7 @@ class CertificateRegenerateTests(CertificateSupportTestCase):
|
||||
|
||||
def test_regenerate_user_has_no_certificate(self):
|
||||
# Delete the user's certificate
|
||||
GeneratedCertificate.objects.all().delete()
|
||||
GeneratedCertificate.eligible_certificates.all().delete()
|
||||
|
||||
# Should be able to regenerate
|
||||
response = self._regenerate(
|
||||
@@ -308,7 +308,7 @@ class CertificateRegenerateTests(CertificateSupportTestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# 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)
|
||||
|
||||
def _regenerate(self, course_key=None, username=None):
|
||||
@@ -412,7 +412,7 @@ class CertificateGenerateTests(CertificateSupportTestCase):
|
||||
|
||||
def test_generate_user_has_no_certificate(self):
|
||||
# Delete the user's certificate
|
||||
GeneratedCertificate.objects.all().delete()
|
||||
GeneratedCertificate.eligible_certificates.all().delete()
|
||||
|
||||
# Should be able to generate
|
||||
response = self._generate(
|
||||
@@ -422,7 +422,7 @@ class CertificateGenerateTests(CertificateSupportTestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# 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)
|
||||
|
||||
def _generate(self, course_key=None, username=None):
|
||||
|
||||
@@ -210,7 +210,7 @@ class MicrositeCertificatesViewsTests(ModuleStoreTestCase):
|
||||
self.user.profile.name = "Joe User"
|
||||
self.user.profile.save()
|
||||
self.client.login(username=self.user.username, password='foo')
|
||||
self.cert = GeneratedCertificate.objects.create(
|
||||
self.cert = GeneratedCertificate.eligible_certificates.create(
|
||||
user=self.user,
|
||||
course_id=self.course_id,
|
||||
download_uuid=uuid4(),
|
||||
|
||||
@@ -13,6 +13,7 @@ from django.core.urlresolvers import reverse
|
||||
from django.test.client import Client
|
||||
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 student.tests.factories import UserFactory, CourseEnrollmentFactory
|
||||
from student.roles import CourseStaffRole
|
||||
@@ -96,7 +97,8 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
|
||||
)
|
||||
CourseEnrollmentFactory.create(
|
||||
user=self.user,
|
||||
course_id=self.course_id
|
||||
course_id=self.course_id,
|
||||
mode=CourseMode.HONOR,
|
||||
)
|
||||
CertificateHtmlViewConfigurationFactory.create()
|
||||
LinkedInAddToProfileConfigurationFactory.create()
|
||||
@@ -378,6 +380,32 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
|
||||
self.assertIn("Cannot Find Certificate", response.content)
|
||||
self.assertIn("We cannot find a certificate with this URL or ID number.", response.content)
|
||||
|
||||
@ddt.data(True, False)
|
||||
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
|
||||
def test_audit_certificate_display(self, eligible_for_certificate):
|
||||
"""
|
||||
Ensure that audit-mode certs are not shown in the web view.
|
||||
"""
|
||||
# Convert the cert to audit, with the specified eligibility
|
||||
self.cert.mode = 'audit'
|
||||
self.cert.eligible_for_certificate = eligible_for_certificate
|
||||
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)
|
||||
def test_html_view_for_invalid_certificate(self):
|
||||
"""
|
||||
@@ -533,7 +561,7 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
|
||||
course_id=unicode(self.course.id)
|
||||
)
|
||||
self.cert.delete()
|
||||
self.assertEqual(len(GeneratedCertificate.objects.all()), 0)
|
||||
self.assertEqual(len(GeneratedCertificate.eligible_certificates.all()), 0)
|
||||
|
||||
response = self.client.get(test_url)
|
||||
self.assertIn('invalid', response.content)
|
||||
@@ -556,7 +584,7 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
|
||||
preview mode. Either the certificate is marked active or not.
|
||||
"""
|
||||
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)
|
||||
test_url = get_certificate_url(
|
||||
user_id=self.user.id,
|
||||
|
||||
@@ -342,7 +342,7 @@ def _get_user_certificate(request, user, course_key, course, preview_mode=None):
|
||||
else:
|
||||
# certificate is being viewed by learner or public
|
||||
try:
|
||||
user_certificate = GeneratedCertificate.objects.get(
|
||||
user_certificate = GeneratedCertificate.eligible_certificates.get(
|
||||
user=user,
|
||||
course_id=course_key,
|
||||
status=CertificateStatuses.downloadable
|
||||
@@ -459,7 +459,7 @@ def render_cert_by_uuid(request, certificate_uuid):
|
||||
This public view generates an HTML representation of the specified certificate
|
||||
"""
|
||||
try:
|
||||
certificate = GeneratedCertificate.objects.get(
|
||||
certificate = GeneratedCertificate.eligible_certificates.get(
|
||||
verify_uuid=certificate_uuid,
|
||||
status=CertificateStatuses.downloadable
|
||||
)
|
||||
|
||||
@@ -75,7 +75,7 @@ def update_certificate(request):
|
||||
try:
|
||||
course_key = SlashSeparatedCourseKey.from_deprecated_string(xqueue_body['course_id'])
|
||||
|
||||
cert = GeneratedCertificate.objects.get(
|
||||
cert = GeneratedCertificate.eligible_certificates.get(
|
||||
user__username=xqueue_body['username'],
|
||||
course_id=course_key,
|
||||
key=xqueue_header['lms_key'])
|
||||
|
||||
@@ -619,7 +619,7 @@ class CertificateExceptionViewInstructorApiTest(SharedModuleStoreTestCase):
|
||||
# Verify that certificate exception successfully removed from CertificateWhitelist and GeneratedCertificate
|
||||
with self.assertRaises(ObjectDoesNotExist):
|
||||
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
|
||||
)
|
||||
|
||||
@@ -1010,7 +1010,7 @@ class CertificateInvalidationViewTests(SharedModuleStoreTestCase):
|
||||
self.fail("The certificate is not invalidated.")
|
||||
|
||||
# Validate generated certificate was invalidated
|
||||
generated_certificate = GeneratedCertificate.objects.get(
|
||||
generated_certificate = GeneratedCertificate.eligible_certificates.get(
|
||||
user=self.enrolled_user_1,
|
||||
course_id=self.course.id,
|
||||
)
|
||||
|
||||
@@ -2785,7 +2785,7 @@ def add_certificate_exception(course_key, student, certificate_exception):
|
||||
}
|
||||
)
|
||||
|
||||
generated_certificate = GeneratedCertificate.objects.filter(
|
||||
generated_certificate = GeneratedCertificate.eligible_certificates.filter(
|
||||
user=student,
|
||||
course_id=course_key,
|
||||
status=CertificateStatuses.downloadable,
|
||||
@@ -2822,7 +2822,10 @@ def remove_certificate_exception(course_key, student):
|
||||
)
|
||||
|
||||
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()
|
||||
except ObjectDoesNotExist:
|
||||
# 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):
|
||||
|
||||
report_run_date = datetime.date.today().strftime("%B %d, %Y")
|
||||
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,
|
||||
status=CertificateStatuses.downloadable
|
||||
).values(*certificate_features).annotate(total_issued_certificate=Count('mode')))
|
||||
|
||||
@@ -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 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,
|
||||
course_id=course_id,
|
||||
status__in=certificate_statuses,
|
||||
|
||||
@@ -1802,7 +1802,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
|
||||
},
|
||||
result
|
||||
)
|
||||
generated_certificates = GeneratedCertificate.objects.filter(
|
||||
generated_certificates = GeneratedCertificate.eligible_certificates.filter(
|
||||
user__in=students,
|
||||
course_id=self.course.id,
|
||||
mode='honor'
|
||||
@@ -1912,7 +1912,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
|
||||
result
|
||||
)
|
||||
|
||||
generated_certificates = GeneratedCertificate.objects.filter(
|
||||
generated_certificates = GeneratedCertificate.eligible_certificates.filter(
|
||||
user__in=students,
|
||||
course_id=self.course.id,
|
||||
mode='honor'
|
||||
|
||||
Reference in New Issue
Block a user