Commit fe4555bc by Peter Fogg

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.
parents 12e8a3ea 60860e3a
...@@ -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' and
eligible_for_certificate=True.
"""
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"
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
LOCK TABLES `django_migrations` WRITE; LOCK TABLES `django_migrations` WRITE;
/*!40000 ALTER TABLE `django_migrations` DISABLE KEYS */; /*!40000 ALTER TABLE `django_migrations` DISABLE KEYS */;
INSERT INTO `django_migrations` VALUES (1,'contenttypes','0001_initial','2015-12-31 14:56:00.696699'),(2,'auth','0001_initial','2015-12-31 14:56:01.170600'),(3,'admin','0001_initial','2015-12-31 14:56:01.299892'),(4,'assessment','0001_initial','2015-12-31 14:56:06.155369'),(5,'contenttypes','0002_remove_content_type_name','2015-12-31 14:56:06.409538'),(6,'auth','0002_alter_permission_name_max_length','2015-12-31 14:56:06.515068'),(7,'auth','0003_alter_user_email_max_length','2015-12-31 14:56:06.610329'),(8,'auth','0004_alter_user_username_opts','2015-12-31 14:56:06.687373'),(9,'auth','0005_alter_user_last_login_null','2015-12-31 14:56:06.813648'),(10,'auth','0006_require_contenttypes_0002','2015-12-31 14:56:06.826022'),(11,'bookmarks','0001_initial','2015-12-31 14:56:07.366960'),(12,'branding','0001_initial','2015-12-31 14:56:07.712825'),(13,'bulk_email','0001_initial','2015-12-31 14:56:08.342193'),(14,'bulk_email','0002_data__load_course_email_template','2015-12-31 14:56:08.505805'),(15,'instructor_task','0001_initial','2015-12-31 14:56:08.912332'),(16,'certificates','0001_initial','2015-12-31 14:56:11.217554'),(17,'certificates','0002_data__certificatehtmlviewconfiguration_data','2015-12-31 14:56:11.244003'),(18,'certificates','0003_data__default_modes','2015-12-31 14:56:11.290756'),(19,'certificates','0004_certificategenerationhistory','2015-12-31 14:56:11.491562'),(20,'certificates','0005_auto_20151208_0801','2015-12-31 14:56:11.626958'),(21,'certificates','0006_certificatetemplateasset_asset_slug','2015-12-31 14:56:11.720518'),(22,'commerce','0001_data__add_ecommerce_service_user','2015-12-31 14:56:11.757252'),(23,'cors_csrf','0001_initial','2015-12-31 14:56:11.926018'),(24,'course_action_state','0001_initial','2015-12-31 14:56:12.330811'),(25,'course_groups','0001_initial','2015-12-31 14:56:13.807718'),(26,'course_modes','0001_initial','2015-12-31 14:56:13.997693'),(27,'course_modes','0002_coursemode_expiration_datetime_is_explicit','2015-12-31 14:56:14.093722'),(28,'course_modes','0003_auto_20151113_1443','2015-12-31 14:56:14.131388'),(29,'course_modes','0004_auto_20151113_1457','2015-12-31 14:56:14.335292'),(30,'course_modes','0005_auto_20151217_0958','2015-12-31 14:56:14.495286'),(31,'course_overviews','0001_initial','2015-12-31 14:56:14.630819'),(32,'course_overviews','0002_add_course_catalog_fields','2015-12-31 14:56:14.982549'),(33,'course_overviews','0003_courseoverviewgeneratedhistory','2015-12-31 14:56:15.030490'),(34,'course_overviews','0004_courseoverview_org','2015-12-31 14:56:15.113722'),(35,'course_overviews','0005_delete_courseoverviewgeneratedhistory','2015-12-31 14:56:15.143943'),(36,'course_overviews','0006_courseoverviewimageset','2015-12-31 14:56:15.254940'),(37,'course_overviews','0007_courseoverviewimageconfig','2015-12-31 14:56:15.450756'),(38,'course_structures','0001_initial','2015-12-31 14:56:15.506341'),(39,'courseware','0001_initial','2015-12-31 14:56:18.346538'),(40,'credit','0001_initial','2015-12-31 14:56:20.423456'),(41,'dark_lang','0001_initial','2015-12-31 14:56:20.691321'),(42,'dark_lang','0002_data__enable_on_install','2015-12-31 14:56:20.722202'),(43,'default','0001_initial','2015-12-31 14:56:21.476817'),(44,'default','0002_add_related_name','2015-12-31 14:56:21.732324'),(45,'default','0003_alter_email_max_length','2015-12-31 14:56:21.814437'),(46,'django_comment_common','0001_initial','2015-12-31 14:56:22.536942'),(47,'django_notify','0001_initial','2015-12-31 14:56:24.596629'),(48,'django_openid_auth','0001_initial','2015-12-31 14:56:24.972976'),(49,'edx_proctoring','0001_initial','2015-12-31 14:56:30.301516'),(50,'edx_proctoring','0002_proctoredexamstudentattempt_is_status_acknowledged','2015-12-31 14:56:30.658999'),(51,'edxval','0001_initial','2015-12-31 14:56:31.901318'),(52,'edxval','0002_data__default_profiles','2015-12-31 14:56:31.985614'),(53,'embargo','0001_initial','2015-12-31 14:56:33.476417'),(54,'embargo','0002_data__add_countries','2015-12-31 14:56:34.169435'),(55,'external_auth','0001_initial','2015-12-31 14:56:34.976587'),(56,'lms_xblock','0001_initial','2015-12-31 14:56:35.342783'),(57,'milestones','0001_initial','2015-12-31 14:56:37.717602'),(58,'milestones','0002_data__seed_relationship_types','2015-12-31 14:56:37.771111'),(59,'mobile_api','0001_initial','2015-12-31 14:56:38.159420'),(60,'notes','0001_initial','2015-12-31 14:56:38.666731'),(61,'oauth2','0001_initial','2015-12-31 14:56:40.986260'),(62,'oauth2_provider','0001_initial','2015-12-31 14:56:41.385485'),(63,'oauth_provider','0001_initial','2015-12-31 14:56:42.439047'),(64,'organizations','0001_initial','2015-12-31 14:56:42.776842'),(65,'programs','0001_initial','2015-12-31 14:56:43.190769'),(66,'programs','0002_programsapiconfig_cache_ttl','2015-12-31 14:56:43.616792'),(67,'programs','0003_auto_20151120_1613','2015-12-31 14:56:45.292878'),(68,'self_paced','0001_initial','2015-12-31 14:56:45.742961'),(69,'sessions','0001_initial','2015-12-31 14:56:45.834774'),(70,'student','0001_initial','2015-12-31 14:57:00.707670'),(71,'shoppingcart','0001_initial','2015-12-31 14:57:15.041286'),(72,'shoppingcart','0002_auto_20151208_1034','2015-12-31 14:57:16.163983'),(73,'shoppingcart','0003_auto_20151217_0958','2015-12-31 14:57:17.314819'),(74,'sites','0001_initial','2015-12-31 14:57:17.389091'),(75,'splash','0001_initial','2015-12-31 14:57:18.037349'),(76,'status','0001_initial','2015-12-31 14:57:19.826410'),(77,'student','0002_auto_20151208_1034','2015-12-31 14:57:21.147521'),(78,'submissions','0001_initial','2015-12-31 14:57:23.354858'),(79,'submissions','0002_auto_20151119_0913','2015-12-31 14:57:23.620278'),(80,'survey','0001_initial','2015-12-31 14:57:24.698404'),(81,'teams','0001_initial','2015-12-31 14:57:26.825637'),(82,'third_party_auth','0001_initial','2015-12-31 14:57:30.487810'),(83,'track','0001_initial','2015-12-31 14:57:30.566408'),(84,'user_api','0001_initial','2015-12-31 14:57:35.956967'),(85,'util','0001_initial','2015-12-31 14:57:36.691350'),(86,'util','0002_data__default_rate_limit_config','2015-12-31 14:57:36.747714'),(87,'verify_student','0001_initial','2015-12-31 14:57:46.279873'),(88,'verify_student','0002_auto_20151124_1024','2015-12-31 14:57:47.187739'),(89,'verify_student','0003_auto_20151113_1443','2015-12-31 14:57:47.983638'),(90,'wiki','0001_initial','2015-12-31 14:58:12.991851'),(91,'wiki','0002_remove_article_subscription','2015-12-31 14:58:13.055035'),(92,'workflow','0001_initial','2015-12-31 14:58:13.504017'),(93,'xblock_django','0001_initial','2015-12-31 14:58:14.361722'),(94,'contentstore','0001_initial','2015-12-31 14:58:40.752928'),(95,'course_creators','0001_initial','2015-12-31 14:58:40.919957'),(96,'xblock_config','0001_initial','2015-12-31 14:58:41.443731'); INSERT INTO `django_migrations` VALUES (1,'contenttypes','0001_initial','2015-12-31 14:56:00.696699'),(2,'auth','0001_initial','2015-12-31 14:56:01.170600'),(3,'admin','0001_initial','2015-12-31 14:56:01.299892'),(4,'assessment','0001_initial','2015-12-31 14:56:06.155369'),(5,'contenttypes','0002_remove_content_type_name','2015-12-31 14:56:06.409538'),(6,'auth','0002_alter_permission_name_max_length','2015-12-31 14:56:06.515068'),(7,'auth','0003_alter_user_email_max_length','2015-12-31 14:56:06.610329'),(8,'auth','0004_alter_user_username_opts','2015-12-31 14:56:06.687373'),(9,'auth','0005_alter_user_last_login_null','2015-12-31 14:56:06.813648'),(10,'auth','0006_require_contenttypes_0002','2015-12-31 14:56:06.826022'),(11,'bookmarks','0001_initial','2015-12-31 14:56:07.366960'),(12,'branding','0001_initial','2015-12-31 14:56:07.712825'),(13,'bulk_email','0001_initial','2015-12-31 14:56:08.342193'),(14,'bulk_email','0002_data__load_course_email_template','2015-12-31 14:56:08.505805'),(15,'instructor_task','0001_initial','2015-12-31 14:56:08.912332'),(16,'certificates','0001_initial','2015-12-31 14:56:11.217554'),(17,'certificates','0002_data__certificatehtmlviewconfiguration_data','2015-12-31 14:56:11.244003'),(18,'certificates','0003_data__default_modes','2015-12-31 14:56:11.290756'),(19,'certificates','0004_certificategenerationhistory','2015-12-31 14:56:11.491562'),(20,'certificates','0005_auto_20151208_0801','2015-12-31 14:56:11.626958'),(21,'certificates','0006_certificatetemplateasset_asset_slug','2015-12-31 14:56:11.720518'),(22,'commerce','0001_data__add_ecommerce_service_user','2015-12-31 14:56:11.757252'),(23,'cors_csrf','0001_initial','2015-12-31 14:56:11.926018'),(24,'course_action_state','0001_initial','2015-12-31 14:56:12.330811'),(25,'course_groups','0001_initial','2015-12-31 14:56:13.807718'),(26,'course_modes','0001_initial','2015-12-31 14:56:13.997693'),(27,'course_modes','0002_coursemode_expiration_datetime_is_explicit','2015-12-31 14:56:14.093722'),(28,'course_modes','0003_auto_20151113_1443','2015-12-31 14:56:14.131388'),(29,'course_modes','0004_auto_20151113_1457','2015-12-31 14:56:14.335292'),(30,'course_modes','0005_auto_20151217_0958','2015-12-31 14:56:14.495286'),(31,'course_overviews','0001_initial','2015-12-31 14:56:14.630819'),(32,'course_overviews','0002_add_course_catalog_fields','2015-12-31 14:56:14.982549'),(33,'course_overviews','0003_courseoverviewgeneratedhistory','2015-12-31 14:56:15.030490'),(34,'course_overviews','0004_courseoverview_org','2015-12-31 14:56:15.113722'),(35,'course_overviews','0005_delete_courseoverviewgeneratedhistory','2015-12-31 14:56:15.143943'),(36,'course_overviews','0006_courseoverviewimageset','2015-12-31 14:56:15.254940'),(37,'course_overviews','0007_courseoverviewimageconfig','2015-12-31 14:56:15.450756'),(38,'course_structures','0001_initial','2015-12-31 14:56:15.506341'),(39,'courseware','0001_initial','2015-12-31 14:56:18.346538'),(40,'credit','0001_initial','2015-12-31 14:56:20.423456'),(41,'dark_lang','0001_initial','2015-12-31 14:56:20.691321'),(42,'dark_lang','0002_data__enable_on_install','2015-12-31 14:56:20.722202'),(43,'default','0001_initial','2015-12-31 14:56:21.476817'),(44,'default','0002_add_related_name','2015-12-31 14:56:21.732324'),(45,'default','0003_alter_email_max_length','2015-12-31 14:56:21.814437'),(46,'django_comment_common','0001_initial','2015-12-31 14:56:22.536942'),(47,'django_notify','0001_initial','2015-12-31 14:56:24.596629'),(48,'django_openid_auth','0001_initial','2015-12-31 14:56:24.972976'),(49,'edx_proctoring','0001_initial','2015-12-31 14:56:30.301516'),(50,'edx_proctoring','0002_proctoredexamstudentattempt_is_status_acknowledged','2015-12-31 14:56:30.658999'),(51,'edxval','0001_initial','2015-12-31 14:56:31.901318'),(52,'edxval','0002_data__default_profiles','2015-12-31 14:56:31.985614'),(53,'embargo','0001_initial','2015-12-31 14:56:33.476417'),(54,'embargo','0002_data__add_countries','2015-12-31 14:56:34.169435'),(55,'external_auth','0001_initial','2015-12-31 14:56:34.976587'),(56,'lms_xblock','0001_initial','2015-12-31 14:56:35.342783'),(57,'milestones','0001_initial','2015-12-31 14:56:37.717602'),(58,'milestones','0002_data__seed_relationship_types','2015-12-31 14:56:37.771111'),(59,'mobile_api','0001_initial','2015-12-31 14:56:38.159420'),(60,'notes','0001_initial','2015-12-31 14:56:38.666731'),(61,'oauth2','0001_initial','2015-12-31 14:56:40.986260'),(62,'oauth2_provider','0001_initial','2015-12-31 14:56:41.385485'),(63,'oauth_provider','0001_initial','2015-12-31 14:56:42.439047'),(64,'organizations','0001_initial','2015-12-31 14:56:42.776842'),(65,'programs','0001_initial','2015-12-31 14:56:43.190769'),(66,'programs','0002_programsapiconfig_cache_ttl','2015-12-31 14:56:43.616792'),(67,'programs','0003_auto_20151120_1613','2015-12-31 14:56:45.292878'),(68,'self_paced','0001_initial','2015-12-31 14:56:45.742961'),(69,'sessions','0001_initial','2015-12-31 14:56:45.834774'),(70,'student','0001_initial','2015-12-31 14:57:00.707670'),(71,'shoppingcart','0001_initial','2015-12-31 14:57:15.041286'),(72,'shoppingcart','0002_auto_20151208_1034','2015-12-31 14:57:16.163983'),(73,'shoppingcart','0003_auto_20151217_0958','2015-12-31 14:57:17.314819'),(74,'sites','0001_initial','2015-12-31 14:57:17.389091'),(75,'splash','0001_initial','2015-12-31 14:57:18.037349'),(76,'status','0001_initial','2015-12-31 14:57:19.826410'),(77,'student','0002_auto_20151208_1034','2015-12-31 14:57:21.147521'),(78,'submissions','0001_initial','2015-12-31 14:57:23.354858'),(79,'submissions','0002_auto_20151119_0913','2015-12-31 14:57:23.620278'),(80,'survey','0001_initial','2015-12-31 14:57:24.698404'),(81,'teams','0001_initial','2015-12-31 14:57:26.825637'),(82,'third_party_auth','0001_initial','2015-12-31 14:57:30.487810'),(83,'track','0001_initial','2015-12-31 14:57:30.566408'),(84,'user_api','0001_initial','2015-12-31 14:57:35.956967'),(85,'util','0001_initial','2015-12-31 14:57:36.691350'),(86,'util','0002_data__default_rate_limit_config','2015-12-31 14:57:36.747714'),(87,'verify_student','0001_initial','2015-12-31 14:57:46.279873'),(88,'verify_student','0002_auto_20151124_1024','2015-12-31 14:57:47.187739'),(89,'verify_student','0003_auto_20151113_1443','2015-12-31 14:57:47.983638'),(90,'wiki','0001_initial','2015-12-31 14:58:12.991851'),(91,'wiki','0002_remove_article_subscription','2015-12-31 14:58:13.055035'),(92,'workflow','0001_initial','2015-12-31 14:58:13.504017'),(93,'xblock_django','0001_initial','2015-12-31 14:58:14.361722'),(94,'contentstore','0001_initial','2015-12-31 14:58:40.752928'),(95,'course_creators','0001_initial','2015-12-31 14:58:40.919957'),(96,'xblock_config','0001_initial','2015-12-31 14:58:41.443731'),(97,'edx_proctoring','0003_auto_20160101_0525','2016-01-07 20:15:02.045764'),(98,'certificates','0007_certificateinvalidation','2016-01-07 20:27:19.565490'),(99,'certificates','0008_generatedcertificate_eligible_for_certificate','2016-01-07 20:27:19.728890');
/*!40000 ALTER TABLE `django_migrations` ENABLE KEYS */; /*!40000 ALTER TABLE `django_migrations` ENABLE KEYS */;
UNLOCK TABLES; UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
...@@ -34,4 +34,4 @@ UNLOCK TABLES; ...@@ -34,4 +34,4 @@ UNLOCK TABLES;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2015-12-31 14:58:46 -- Dump completed on 2016-01-07 20:27:41
...@@ -190,7 +190,7 @@ CREATE TABLE `assessment_assessmentpart` ( ...@@ -190,7 +190,7 @@ CREATE TABLE `assessment_assessmentpart` (
`feedback` longtext NOT NULL, `feedback` longtext NOT NULL,
`assessment_id` int(11) NOT NULL, `assessment_id` int(11) NOT NULL,
`criterion_id` int(11) NOT NULL, `criterion_id` int(11) NOT NULL,
`option_id` int(11), `option_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `asses_assessment_id_1d752290138ce479_fk_assessment_assessment_id` (`assessment_id`), KEY `asses_assessment_id_1d752290138ce479_fk_assessment_assessment_id` (`assessment_id`),
KEY `assessment_assessmentpart_385b00a3` (`criterion_id`), KEY `assessment_assessmentpart_385b00a3` (`criterion_id`),
...@@ -386,7 +386,7 @@ CREATE TABLE `auth_permission` ( ...@@ -386,7 +386,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=710 DEFAULT CHARSET=utf8; ) ENGINE=InnoDB AUTO_INCREMENT=716 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 */;
...@@ -418,7 +418,7 @@ CREATE TABLE `auth_user` ( ...@@ -418,7 +418,7 @@ CREATE TABLE `auth_user` (
`date_joined` datetime(6) NOT NULL, `date_joined` datetime(6) NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`) 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 */; /*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `auth_user_groups`; DROP TABLE IF EXISTS `auth_user_groups`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
...@@ -588,7 +588,7 @@ CREATE TABLE `bulk_email_courseemailtemplate` ( ...@@ -588,7 +588,7 @@ CREATE TABLE `bulk_email_courseemailtemplate` (
`name` varchar(255) DEFAULT NULL, `name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`) 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 */; /*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `bulk_email_optout`; DROP TABLE IF EXISTS `bulk_email_optout`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
...@@ -659,7 +659,7 @@ CREATE TABLE `certificates_badgeimageconfiguration` ( ...@@ -659,7 +659,7 @@ CREATE TABLE `certificates_badgeimageconfiguration` (
`default` tinyint(1) NOT NULL, `default` tinyint(1) NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `mode` (`mode`) 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 */; /*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `certificates_certificategenerationconfiguration`; DROP TABLE IF EXISTS `certificates_certificategenerationconfiguration`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
...@@ -717,7 +717,25 @@ CREATE TABLE `certificates_certificatehtmlviewconfiguration` ( ...@@ -717,7 +717,25 @@ CREATE TABLE `certificates_certificatehtmlviewconfiguration` (
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `certificates_cert_changed_by_id_1de6cf549bca749b_fk_auth_user_id` (`changed_by_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`) 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 */; /*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `certificates_certificatetemplate`; DROP TABLE IF EXISTS `certificates_certificatetemplate`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
...@@ -748,7 +766,7 @@ CREATE TABLE `certificates_certificatetemplateasset` ( ...@@ -748,7 +766,7 @@ CREATE TABLE `certificates_certificatetemplateasset` (
`modified` datetime(6) NOT NULL, `modified` datetime(6) NOT NULL,
`description` varchar(255) DEFAULT NULL, `description` varchar(255) DEFAULT NULL,
`asset` varchar(255) NOT NULL, `asset` varchar(255) NOT NULL,
`asset_slug` varchar(255), `asset_slug` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `asset_slug` (`asset_slug`) UNIQUE KEY `asset_slug` (`asset_slug`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
...@@ -822,6 +840,7 @@ CREATE TABLE `certificates_generatedcertificate` ( ...@@ -822,6 +840,7 @@ CREATE TABLE `certificates_generatedcertificate` (
`modified_date` datetime(6) NOT NULL, `modified_date` datetime(6) NOT NULL,
`error_reason` varchar(512) NOT NULL, `error_reason` varchar(512) NOT NULL,
`user_id` int(11) NOT NULL, `user_id` int(11) NOT NULL,
`eligible_for_certificate` tinyint(1) NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `certificates_generatedcertificate_user_id_552a0fa6f7d3f7e8_uniq` (`user_id`,`course_id`), UNIQUE KEY `certificates_generatedcertificate_user_id_552a0fa6f7d3f7e8_uniq` (`user_id`,`course_id`),
KEY `certificates_generatedcertific_verify_uuid_1b5a14bb83c471ff_uniq` (`verify_uuid`), KEY `certificates_generatedcertific_verify_uuid_1b5a14bb83c471ff_uniq` (`verify_uuid`),
...@@ -1089,7 +1108,7 @@ CREATE TABLE `course_overviews_courseoverview` ( ...@@ -1089,7 +1108,7 @@ CREATE TABLE `course_overviews_courseoverview` (
`enrollment_domain` longtext, `enrollment_domain` longtext,
`invitation_only` tinyint(1) NOT NULL, `invitation_only` tinyint(1) NOT NULL,
`max_student_enrollments_allowed` int(11) DEFAULT NULL, `max_student_enrollments_allowed` int(11) DEFAULT NULL,
`announcement` datetime(6), `announcement` datetime(6) DEFAULT NULL,
`catalog_visibility` longtext, `catalog_visibility` longtext,
`course_video_url` longtext, `course_video_url` longtext,
`effort` longtext, `effort` longtext,
...@@ -1479,7 +1498,7 @@ CREATE TABLE `dark_lang_darklangconfig` ( ...@@ -1479,7 +1498,7 @@ CREATE TABLE `dark_lang_darklangconfig` (
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `dark_lang_darklan_changed_by_id_7e1defb1121d58b8_fk_auth_user_id` (`changed_by_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`) 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 */; /*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `django_admin_log`; DROP TABLE IF EXISTS `django_admin_log`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
...@@ -1556,7 +1575,7 @@ CREATE TABLE `django_content_type` ( ...@@ -1556,7 +1575,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=236 DEFAULT CHARSET=utf8; ) ENGINE=InnoDB AUTO_INCREMENT=238 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 */;
...@@ -1567,7 +1586,7 @@ CREATE TABLE `django_migrations` ( ...@@ -1567,7 +1586,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=97 DEFAULT CHARSET=utf8; ) ENGINE=InnoDB AUTO_INCREMENT=100 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 */;
...@@ -1769,7 +1788,7 @@ CREATE TABLE `edxval_profile` ( ...@@ -1769,7 +1788,7 @@ CREATE TABLE `edxval_profile` (
`profile_name` varchar(50) NOT NULL, `profile_name` varchar(50) NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `profile_name` (`profile_name`) 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 */; /*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `edxval_subtitle`; DROP TABLE IF EXISTS `edxval_subtitle`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
...@@ -1813,7 +1832,7 @@ CREATE TABLE `embargo_country` ( ...@@ -1813,7 +1832,7 @@ CREATE TABLE `embargo_country` (
`country` varchar(2) NOT NULL, `country` varchar(2) NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `country` (`country`) 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 */; /*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `embargo_countryaccessrule`; DROP TABLE IF EXISTS `embargo_countryaccessrule`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
...@@ -2033,7 +2052,7 @@ CREATE TABLE `milestones_milestonerelationshiptype` ( ...@@ -2033,7 +2052,7 @@ CREATE TABLE `milestones_milestonerelationshiptype` (
`active` tinyint(1) NOT NULL, `active` tinyint(1) NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`) 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 */; /*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `milestones_usermilestone`; DROP TABLE IF EXISTS `milestones_usermilestone`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
...@@ -2104,7 +2123,7 @@ CREATE TABLE `notify_notification` ( ...@@ -2104,7 +2123,7 @@ CREATE TABLE `notify_notification` (
`is_viewed` tinyint(1) NOT NULL, `is_viewed` tinyint(1) NOT NULL,
`is_emailed` tinyint(1) NOT NULL, `is_emailed` tinyint(1) NOT NULL,
`created` datetime(6) NOT NULL, `created` datetime(6) NOT NULL,
`subscription_id` int(11), `subscription_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `notify_notification_ef42673f` (`subscription_id`), KEY `notify_notification_ef42673f` (`subscription_id`),
CONSTRAINT `D48032390695e0699e92b8d7ccdbff7e` FOREIGN KEY (`subscription_id`) REFERENCES `notify_subscription` (`subscription_id`) CONSTRAINT `D48032390695e0699e92b8d7ccdbff7e` FOREIGN KEY (`subscription_id`) REFERENCES `notify_subscription` (`subscription_id`)
...@@ -2679,9 +2698,9 @@ CREATE TABLE `shoppingcart_courseregistrationcode` ( ...@@ -2679,9 +2698,9 @@ CREATE TABLE `shoppingcart_courseregistrationcode` (
`mode_slug` varchar(100) DEFAULT NULL, `mode_slug` varchar(100) DEFAULT NULL,
`is_valid` tinyint(1) NOT NULL, `is_valid` tinyint(1) NOT NULL,
`created_by_id` int(11) NOT NULL, `created_by_id` int(11) NOT NULL,
`invoice_id` int(11), `invoice_id` int(11) DEFAULT NULL,
`order_id` int(11), `order_id` int(11) DEFAULT NULL,
`invoice_item_id` int(11), `invoice_item_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `code` (`code`), UNIQUE KEY `code` (`code`),
KEY `shoppingcart_cour_created_by_id_11125a9667aa01c9_fk_auth_user_id` (`created_by_id`), KEY `shoppingcart_cour_created_by_id_11125a9667aa01c9_fk_auth_user_id` (`created_by_id`),
...@@ -2995,6 +3014,20 @@ CREATE TABLE `splash_splashconfig` ( ...@@ -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`) 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; ) 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 `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`; DROP TABLE IF EXISTS `status_coursemessage`;
/*!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 */;
...@@ -3336,7 +3369,7 @@ CREATE TABLE `submissions_score` ( ...@@ -3336,7 +3369,7 @@ CREATE TABLE `submissions_score` (
`created_at` datetime(6) NOT NULL, `created_at` datetime(6) NOT NULL,
`reset` tinyint(1) NOT NULL, `reset` tinyint(1) NOT NULL,
`student_item_id` int(11) NOT NULL, `student_item_id` int(11) NOT NULL,
`submission_id` int(11), `submission_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `submissions_score_fde81f11` (`created_at`), KEY `submissions_score_fde81f11` (`created_at`),
KEY `submissions_score_02d5e83e` (`student_item_id`), KEY `submissions_score_02d5e83e` (`student_item_id`),
...@@ -3697,7 +3730,7 @@ CREATE TABLE `util_ratelimitconfiguration` ( ...@@ -3697,7 +3730,7 @@ CREATE TABLE `util_ratelimitconfiguration` (
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `util_ratelimitcon_changed_by_id_2c8891cb4854f3b5_fk_auth_user_id` (`changed_by_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`) 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 */; /*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `verify_student_historicalverificationdeadline`; DROP TABLE IF EXISTS `verify_student_historicalverificationdeadline`;
/*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET @saved_cs_client = @@character_set_client */;
...@@ -3870,9 +3903,9 @@ CREATE TABLE `wiki_article` ( ...@@ -3870,9 +3903,9 @@ CREATE TABLE `wiki_article` (
`group_write` tinyint(1) NOT NULL, `group_write` tinyint(1) NOT NULL,
`other_read` tinyint(1) NOT NULL, `other_read` tinyint(1) NOT NULL,
`other_write` tinyint(1) NOT NULL, `other_write` tinyint(1) NOT NULL,
`current_revision_id` int(11), `current_revision_id` int(11) DEFAULT NULL,
`group_id` int(11), `group_id` int(11) DEFAULT NULL,
`owner_id` int(11), `owner_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `current_revision_id` (`current_revision_id`), UNIQUE KEY `current_revision_id` (`current_revision_id`),
KEY `wiki_article_0e939a4f` (`group_id`), KEY `wiki_article_0e939a4f` (`group_id`),
...@@ -3944,7 +3977,7 @@ DROP TABLE IF EXISTS `wiki_attachment`; ...@@ -3944,7 +3977,7 @@ DROP TABLE IF EXISTS `wiki_attachment`;
CREATE TABLE `wiki_attachment` ( CREATE TABLE `wiki_attachment` (
`reusableplugin_ptr_id` int(11) NOT NULL, `reusableplugin_ptr_id` int(11) NOT NULL,
`original_filename` varchar(256) DEFAULT NULL, `original_filename` varchar(256) DEFAULT NULL,
`current_revision_id` int(11), `current_revision_id` int(11) DEFAULT NULL,
PRIMARY KEY (`reusableplugin_ptr_id`), PRIMARY KEY (`reusableplugin_ptr_id`),
UNIQUE KEY `current_revision_id` (`current_revision_id`), UNIQUE KEY `current_revision_id` (`current_revision_id`),
CONSTRAINT `D32d32ecb0471dc863a4e19562842024` FOREIGN KEY (`current_revision_id`) REFERENCES `wiki_attachmentrevision` (`id`), CONSTRAINT `D32d32ecb0471dc863a4e19562842024` FOREIGN KEY (`current_revision_id`) REFERENCES `wiki_attachmentrevision` (`id`),
...@@ -3967,8 +4000,8 @@ CREATE TABLE `wiki_attachmentrevision` ( ...@@ -3967,8 +4000,8 @@ CREATE TABLE `wiki_attachmentrevision` (
`file` varchar(100) NOT NULL, `file` varchar(100) NOT NULL,
`description` longtext NOT NULL, `description` longtext NOT NULL,
`attachment_id` int(11) NOT NULL, `attachment_id` int(11) NOT NULL,
`previous_revision_id` int(11), `previous_revision_id` int(11) DEFAULT NULL,
`user_id` int(11), `user_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `wiki_attachmentrevision_07ba63f5` (`attachment_id`), KEY `wiki_attachmentrevision_07ba63f5` (`attachment_id`),
KEY `wiki_attachmentrevision_e8680b8a` (`previous_revision_id`), KEY `wiki_attachmentrevision_e8680b8a` (`previous_revision_id`),
...@@ -4027,7 +4060,7 @@ DROP TABLE IF EXISTS `wiki_revisionplugin`; ...@@ -4027,7 +4060,7 @@ DROP TABLE IF EXISTS `wiki_revisionplugin`;
/*!40101 SET character_set_client = utf8 */; /*!40101 SET character_set_client = utf8 */;
CREATE TABLE `wiki_revisionplugin` ( CREATE TABLE `wiki_revisionplugin` (
`articleplugin_ptr_id` int(11) NOT NULL, `articleplugin_ptr_id` int(11) NOT NULL,
`current_revision_id` int(11), `current_revision_id` int(11) DEFAULT NULL,
PRIMARY KEY (`articleplugin_ptr_id`), PRIMARY KEY (`articleplugin_ptr_id`),
UNIQUE KEY `current_revision_id` (`current_revision_id`), UNIQUE KEY `current_revision_id` (`current_revision_id`),
CONSTRAINT `D03d76148e98b4bc99e3137189894366` FOREIGN KEY (`current_revision_id`) REFERENCES `wiki_revisionpluginrevision` (`id`), CONSTRAINT `D03d76148e98b4bc99e3137189894366` FOREIGN KEY (`current_revision_id`) REFERENCES `wiki_revisionpluginrevision` (`id`),
...@@ -4048,8 +4081,8 @@ CREATE TABLE `wiki_revisionpluginrevision` ( ...@@ -4048,8 +4081,8 @@ CREATE TABLE `wiki_revisionpluginrevision` (
`deleted` tinyint(1) NOT NULL, `deleted` tinyint(1) NOT NULL,
`locked` tinyint(1) NOT NULL, `locked` tinyint(1) NOT NULL,
`plugin_id` int(11) NOT NULL, `plugin_id` int(11) NOT NULL,
`previous_revision_id` int(11), `previous_revision_id` int(11) DEFAULT NULL,
`user_id` int(11), `user_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `wiki_revisionpluginrevision_b25eaab4` (`plugin_id`), KEY `wiki_revisionpluginrevision_b25eaab4` (`plugin_id`),
KEY `wiki_revisionpluginrevision_e8680b8a` (`previous_revision_id`), KEY `wiki_revisionpluginrevision_e8680b8a` (`previous_revision_id`),
......
...@@ -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(
student,
course_key,
course=course, course=course,
generate_pdf=generate_pdf, generate_pdf=generate_pdf,
forced_grade=forced_grade) forced_grade=forced_grade
if status in [CertificateStatuses.generating, CertificateStatuses.downloadable]: )
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)
......
# -*- 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): ...@@ -143,7 +143,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 +168,40 @@ class CertificateWhitelist(models.Model): ...@@ -168,11 +168,40 @@ 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().filter(eligible_for_certificate=True)
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]
...@@ -191,6 +220,19 @@ class GeneratedCertificate(models.Model): ...@@ -191,6 +220,19 @@ class GeneratedCertificate(models.Model):
created_date = models.DateTimeField(auto_now_add=True) created_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now=True) modified_date = models.DateTimeField(auto_now=True)
error_reason = models.CharField(max_length=512, blank=True, default='') 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): class Meta(object):
unique_together = (('user', 'course_id'),) unique_together = (('user', 'course_id'),)
...@@ -410,7 +452,7 @@ def certificate_status_for_student(student, course_id): ...@@ -410,7 +452,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,
......
...@@ -20,6 +20,7 @@ from student.models import UserProfile, CourseEnrollment ...@@ -20,6 +20,7 @@ from student.models import UserProfile, CourseEnrollment
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
from certificates.models import ( from certificates.models import (
CertificateStatuses,
GeneratedCertificate, GeneratedCertificate,
certificate_status_for_student, certificate_status_for_student,
CertificateStatuses as status, CertificateStatuses as status,
...@@ -120,14 +121,14 @@ class XQueueCertInterface(object): ...@@ -120,14 +121,14 @@ class XQueueCertInterface(object):
Change the certificate status to unavailable (if it exists) and request Change the certificate status to unavailable (if it exists) and request
grading. Passing grades will put a certificate request on the queue. 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 # TODO: when del_cert is implemented and plumbed through certificates
# repo also, do a deletion followed by a creation r/t a simple # repo also, do a deletion followed by a creation r/t a simple
# recreation. XXX: this leaves orphan cert files laying around in # recreation. XXX: this leaves orphan cert files laying around in
# AWS. See note in the docstring too. # AWS. See note in the docstring too.
try: try:
certificate = GeneratedCertificate.objects.get(user=student, course_id=course_id) certificate = GeneratedCertificate.eligible_certificates.get(user=student, course_id=course_id)
LOGGER.info( LOGGER.info(
( (
...@@ -183,8 +184,7 @@ class XQueueCertInterface(object): ...@@ -183,8 +184,7 @@ class XQueueCertInterface(object):
raise NotImplementedError raise NotImplementedError
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
def add_cert(self, student, course_id, course=None, forced_grade=None, template_file=None, def add_cert(self, student, course_id, course=None, forced_grade=None, template_file=None, generate_pdf=True):
title='None', generate_pdf=True):
""" """
Request a new certificate for a student. Request a new certificate for a student.
...@@ -211,7 +211,7 @@ class XQueueCertInterface(object): ...@@ -211,7 +211,7 @@ class XQueueCertInterface(object):
If a student does not have a passing grade the status If a student does not have a passing grade the status
will change to status.notpassing will change to status.notpassing
Returns the student's status and newly created certificate instance Returns the newly created certificate instance
""" """
valid_statuses = [ valid_statuses = [
...@@ -224,7 +224,6 @@ class XQueueCertInterface(object): ...@@ -224,7 +224,6 @@ class XQueueCertInterface(object):
] ]
cert_status = certificate_status_for_student(student, course_id)['status'] cert_status = certificate_status_for_student(student, course_id)['status']
new_status = cert_status
cert = None cert = None
if cert_status not in valid_statuses: if cert_status not in valid_statuses:
...@@ -239,33 +238,36 @@ class XQueueCertInterface(object): ...@@ -239,33 +238,36 @@ class XQueueCertInterface(object):
cert_status, cert_status,
unicode(valid_statuses) unicode(valid_statuses)
) )
else: return None
# grade the student
# re-use the course passed in optionally so we don't have to re-fetch everything # The caller can optionally pass a course in to avoid
# for every student # re-fetching it from Mongo. If they have not provided one,
# get it from the modulestore.
if course is None: if course is None:
course = modulestore().get_course(course_id, depth=0) course = modulestore().get_course(course_id, depth=0)
profile = UserProfile.objects.get(user=student) profile = UserProfile.objects.get(user=student)
profile_name = profile.name profile_name = profile.name
# Needed # Needed for access control in grading.
self.request.user = student self.request.user = student
self.request.session = {} self.request.session = {}
course_name = course.display_name or unicode(course_id)
is_whitelisted = self.whitelist.filter(user=student, course_id=course_id, whitelist=True).exists() is_whitelisted = self.whitelist.filter(user=student, course_id=course_id, whitelist=True).exists()
grade = grades.grade(student, self.request, course) grade = grades.grade(student, self.request, course)
enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(student, course_id) enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(student, course_id)
mode_is_verified = enrollment_mode in GeneratedCertificate.VERIFIED_CERTS_MODES mode_is_verified = enrollment_mode in GeneratedCertificate.VERIFIED_CERTS_MODES
user_is_verified = SoftwareSecurePhotoVerification.user_is_verified(student) user_is_verified = SoftwareSecurePhotoVerification.user_is_verified(student)
cert_mode = enrollment_mode cert_mode = enrollment_mode
is_eligible_for_certificate = is_whitelisted or CourseMode.is_eligible_for_certificate(enrollment_mode)
# For credit mode generate verified certificate # For credit mode generate verified certificate
if cert_mode == CourseMode.CREDIT_MODE: if cert_mode == CourseMode.CREDIT_MODE:
cert_mode = CourseMode.VERIFIED cert_mode = CourseMode.VERIFIED
if mode_is_verified and user_is_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) template_pdf = "certificate-template-{id.org}-{id.course}-verified.pdf".format(id=course_id)
elif mode_is_verified and not user_is_verified: elif mode_is_verified and not user_is_verified:
template_pdf = "certificate-template-{id.org}-{id.course}.pdf".format(id=course_id) template_pdf = "certificate-template-{id.org}-{id.course}.pdf".format(id=course_id)
...@@ -276,7 +278,7 @@ class XQueueCertInterface(object): ...@@ -276,7 +278,7 @@ class XQueueCertInterface(object):
if forced_grade: if forced_grade:
grade['grade'] = forced_grade grade['grade'] = forced_grade
cert, __ = GeneratedCertificate.objects.get_or_create(user=student, course_id=course_id) cert, __ = GeneratedCertificate.eligible_certificates.get_or_create(user=student, course_id=course_id)
cert.mode = cert_mode cert.mode = cert_mode
cert.user = student cert.user = student
...@@ -284,6 +286,21 @@ class XQueueCertInterface(object): ...@@ -284,6 +286,21 @@ class XQueueCertInterface(object):
cert.course_id = course_id cert.course_id = course_id
cert.name = profile_name cert.name = profile_name
cert.download_url = '' cert.download_url = ''
# 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
# Strip HTML from grade range label # Strip HTML from grade range label
grade_contents = grade.get('grade', None) grade_contents = grade.get('grade', None)
try: try:
...@@ -303,26 +320,35 @@ class XQueueCertInterface(object): ...@@ -303,26 +320,35 @@ class XQueueCertInterface(object):
unicode(exc) unicode(exc)
) )
# Despite blowing up the xml parser, bad values here are fine # Log if the student is whitelisted
grade_contents = None
if is_whitelisted or grade_contents is not None:
if is_whitelisted: if is_whitelisted:
LOGGER.info( LOGGER.info(
u"Student %s is whitelisted in '%s'", u"Student %s is whitelisted in '%s'",
student.id, student.id,
unicode(course_id) unicode(course_id)
) )
# If they are not, short-circuit and don't generate cert
else:
cert.status = status.notpassing
cert.save()
# check to see whether the student is on the LOGGER.info(
# the embargoed country restricted list (
# otherwise, put a new certificate request u"Student %s does not have a grade for '%s', "
# on the queue u"so their certificate status has been set to '%s'. "
u"No certificate generation task was sent to the XQueue."
),
student.id,
unicode(course_id),
cert.status
)
return 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(): if self.restricted.filter(user=student).exists():
new_status = status.restricted cert.status = status.restricted
cert.status = new_status
cert.save() cert.save()
LOGGER.info( LOGGER.info(
...@@ -333,38 +359,45 @@ class XQueueCertInterface(object): ...@@ -333,38 +359,45 @@ class XQueueCertInterface(object):
u"No certificate generation task was sent to the XQueue." u"No certificate generation task was sent to the XQueue."
), ),
student.id, student.id,
new_status, cert.status,
unicode(course_id) unicode(course_id)
) )
else: 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()) key = make_hashkey(random.random())
cert.key = key cert.key = key
contents = { contents = {
'action': 'create', 'action': 'create',
'username': student.username, 'username': student.username,
'course_id': unicode(course_id), 'course_id': course_id,
'course_name': course_name, 'course_name': course.display_name or course_id,
'name': profile_name, 'name': cert.name,
'grade': grade_contents, 'grade': grade_contents,
'template_pdf': template_pdf, 'template_pdf': template_pdf,
} }
if template_file:
contents['template_pdf'] = template_file
if generate_pdf: if generate_pdf:
new_status = status.generating cert.status = status.generating
else: else:
new_status = status.downloadable cert.status = status.downloadable
cert.verify_uuid = uuid4().hex cert.verify_uuid = uuid4().hex
cert.status = new_status
cert.save() cert.save()
if generate_pdf: if generate_pdf:
try: try:
self._send_to_xqueue(contents, key) self._send_to_xqueue(contents, key)
except XQueueAddToQueueError as exc: except XQueueAddToQueueError as exc:
new_status = ExampleCertificate.STATUS_ERROR cert.status = ExampleCertificate.STATUS_ERROR
cert.status = new_status
cert.error_reason = unicode(exc) cert.error_reason = unicode(exc)
cert.save() cert.save()
LOGGER.critical( LOGGER.critical(
...@@ -382,26 +415,10 @@ class XQueueCertInterface(object): ...@@ -382,26 +415,10 @@ class XQueueCertInterface(object):
u"Sent a certificate grading task to the XQueue " u"Sent a certificate grading task to the XQueue "
u"with the key '%s'. " u"with the key '%s'. "
), ),
new_status, cert.status,
key key
) )
else: return cert
new_status = status.notpassing
cert.status = new_status
cert.save()
LOGGER.info(
(
u"Student %s does not have a grade for '%s', "
u"so their certificate status has been set to '%s'. "
u"No certificate generation task was sent to the XQueue."
),
student.id,
unicode(course_id),
new_status
)
return new_status, cert
def add_example_cert(self, example_cert): def add_example_cert(self, example_cert):
"""Add a task to create an example certificate. """Add a task to create an example certificate.
......
...@@ -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,20 @@ from django.test.utils import override_settings ...@@ -8,13 +8,20 @@ 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,
)
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 +241,42 @@ class CertificateTemplateAssetTest(TestCase): ...@@ -234,3 +241,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(
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 ...@@ -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
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 assert_queue_response(self, mode, expected_mode, expected_template_name): def test_ineligible_cert_whitelisted(self):
"""Dry method for course enrollment and adding request to queue.""" """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( CourseEnrollmentFactory(
user=self.user_2, user=self.user_2,
course_id=self.course.id, course_id=self.course.id,
...@@ -109,19 +145,42 @@ class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase): ...@@ -109,19 +145,42 @@ 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.assertFalse(certificate.eligible_for_certificate)
self.assertEqual(certificate.mode, expected_mode)
@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,32 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase): ...@@ -378,6 +380,32 @@ 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(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) @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 +561,7 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase): ...@@ -533,7 +561,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 +584,7 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase): ...@@ -556,7 +584,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,
) )
......
...@@ -2785,7 +2785,7 @@ def add_certificate_exception(course_key, student, certificate_exception): ...@@ -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, user=student,
course_id=course_key, course_id=course_key,
status=CertificateStatuses.downloadable, status=CertificateStatuses.downloadable,
...@@ -2822,7 +2822,10 @@ def remove_certificate_exception(course_key, student): ...@@ -2822,7 +2822,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