Merge pull request #11569 from edx/jibsheet/csmh-extended
Student Module History Extension
This commit is contained in:
@@ -80,6 +80,14 @@ DATABASES = {
|
||||
'timeout': 30,
|
||||
},
|
||||
'ATOMIC_REQUESTS': True,
|
||||
},
|
||||
'student_module_history': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': TEST_ROOT / "db" / "test_student_module_history.db",
|
||||
'TEST_NAME': TEST_ROOT / "db" / "test_student_module_history.db",
|
||||
'OPTIONS': {
|
||||
'timeout': 30,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,5 +26,5 @@ if DB_OVERRIDES['PASSWORD'] is None:
|
||||
raise ImproperlyConfigured("No database password was provided for running "
|
||||
"migrations. This is fatal.")
|
||||
|
||||
for override, value in DB_OVERRIDES.iteritems():
|
||||
DATABASES['default'][override] = value
|
||||
DATABASES['default'].update(DB_OVERRIDES)
|
||||
DATABASES['student_module_history'].update(DB_OVERRIDES)
|
||||
|
||||
@@ -30,6 +30,14 @@
|
||||
"PASSWORD": "",
|
||||
"PORT": "3306",
|
||||
"USER": "root"
|
||||
},
|
||||
"student_module_history": {
|
||||
"ENGINE": "django.db.backends.mysql",
|
||||
"HOST": "localhost",
|
||||
"NAME": "student_module_history_test",
|
||||
"PASSWORD": "",
|
||||
"PORT": "3306",
|
||||
"USER": "root"
|
||||
}
|
||||
},
|
||||
"DOC_STORE_CONFIG": {
|
||||
|
||||
@@ -1114,6 +1114,11 @@ PROCTORING_BACKEND_PROVIDER = {
|
||||
}
|
||||
PROCTORING_SETTINGS = {}
|
||||
|
||||
############################ Global Database Configuration #####################
|
||||
|
||||
DATABASE_ROUTERS = [
|
||||
'openedx.core.lib.django_courseware_routers.StudentModuleHistoryExtendedRouter',
|
||||
]
|
||||
|
||||
############################ OAUTH2 Provider ###################################
|
||||
|
||||
|
||||
@@ -93,11 +93,23 @@ class CapaModule(CapaMixin, XModule):
|
||||
result = handlers[dispatch](data)
|
||||
|
||||
except NotFoundError as err:
|
||||
_, _, traceback_obj = sys.exc_info()
|
||||
log.exception(
|
||||
"Unable to find data when dispatching %s to %s for user %s",
|
||||
dispatch,
|
||||
self.scope_ids.usage_id,
|
||||
self.scope_ids.user_id
|
||||
)
|
||||
_, _, traceback_obj = sys.exc_info() # pylint: disable=redefined-outer-name
|
||||
raise ProcessingError(not_found_error_message), None, traceback_obj
|
||||
|
||||
except Exception as err:
|
||||
_, _, traceback_obj = sys.exc_info()
|
||||
log.exception(
|
||||
"Unknown error when dispatching %s to %s for user %s",
|
||||
dispatch,
|
||||
self.scope_ids.usage_id,
|
||||
self.scope_ids.user_id
|
||||
)
|
||||
_, _, traceback_obj = sys.exc_info() # pylint: disable=redefined-outer-name
|
||||
raise ProcessingError(generic_error_message), None, traceback_obj
|
||||
|
||||
after = self.get_progress()
|
||||
|
||||
@@ -265,6 +265,8 @@ class SharedModuleStoreTestCase(TestCase):
|
||||
for Django ORM models that will get cleaned up properly.
|
||||
"""
|
||||
MODULESTORE = mixed_store_config(mkdtemp_clean(), {}, include_xml=False)
|
||||
# Tell Django to clean out all databases, not just default
|
||||
multi_db = True
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
@@ -392,6 +394,8 @@ class ModuleStoreTestCase(TestCase):
|
||||
"""
|
||||
|
||||
MODULESTORE = mixed_store_config(mkdtemp_clean(), {}, include_xml=False)
|
||||
# Tell Django to clean out all databases, not just default
|
||||
multi_db = True
|
||||
|
||||
def setUp(self, **kwargs):
|
||||
"""
|
||||
|
||||
File diff suppressed because one or more lines are too long
1
common/test/db_cache/bok_choy_data_default.json
Normal file
1
common/test/db_cache/bok_choy_data_default.json
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
[]
|
||||
File diff suppressed because one or more lines are too long
37
common/test/db_cache/bok_choy_migrations_data_default.sql
Normal file
37
common/test/db_cache/bok_choy_migrations_data_default.sql
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -20,8 +20,8 @@ CREATE TABLE `assessment_aiclassifier` (
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `assessment_aiclassifier_962f069f` (`classifier_set_id`),
|
||||
KEY `assessment_aiclassifier_385b00a3` (`criterion_id`),
|
||||
CONSTRAINT `D3bd45d5e3c9cfdc4f3b442119adebe8` FOREIGN KEY (`classifier_set_id`) REFERENCES `assessment_aiclassifierset` (`id`),
|
||||
CONSTRAINT `assessm_criterion_id_275db29f2a0e1711_fk_assessment_criterion_id` FOREIGN KEY (`criterion_id`) REFERENCES `assessment_criterion` (`id`)
|
||||
CONSTRAINT `assessm_criterion_id_275db29f2a0e1711_fk_assessment_criterion_id` FOREIGN KEY (`criterion_id`) REFERENCES `assessment_criterion` (`id`),
|
||||
CONSTRAINT `D3bd45d5e3c9cfdc4f3b442119adebe8` FOREIGN KEY (`classifier_set_id`) REFERENCES `assessment_aiclassifierset` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `assessment_aiclassifierset`;
|
||||
@@ -72,9 +72,9 @@ CREATE TABLE `assessment_aigradingworkflow` (
|
||||
KEY `assessment_aigradingworkflow_a4079fcf` (`assessment_id`),
|
||||
KEY `assessment_aigradingworkflow_962f069f` (`classifier_set_id`),
|
||||
KEY `assessment_aigradingworkflow_8980b7ae` (`rubric_id`),
|
||||
CONSTRAINT `D4d9bca115376aeb07fd970155499db3` FOREIGN KEY (`classifier_set_id`) REFERENCES `assessment_aiclassifierset` (`id`),
|
||||
CONSTRAINT `assessment_ai_rubric_id_3fc938e9e3ae7b2d_fk_assessment_rubric_id` FOREIGN KEY (`rubric_id`) REFERENCES `assessment_rubric` (`id`),
|
||||
CONSTRAINT `asses_assessment_id_68b86880a7f62f1c_fk_assessment_assessment_id` FOREIGN KEY (`assessment_id`) REFERENCES `assessment_assessment` (`id`),
|
||||
CONSTRAINT `assessment_ai_rubric_id_3fc938e9e3ae7b2d_fk_assessment_rubric_id` FOREIGN KEY (`rubric_id`) REFERENCES `assessment_rubric` (`id`)
|
||||
CONSTRAINT `D4d9bca115376aeb07fd970155499db3` FOREIGN KEY (`classifier_set_id`) REFERENCES `assessment_aiclassifierset` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `assessment_aitrainingworkflow`;
|
||||
@@ -110,8 +110,8 @@ CREATE TABLE `assessment_aitrainingworkflow_training_examples` (
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `aitrainingworkflow_id` (`aitrainingworkflow_id`,`trainingexample_id`),
|
||||
KEY `ff4ddecc43bd06c0d85785a61e955133` (`trainingexample_id`),
|
||||
CONSTRAINT `da55be90caee21d95136e40c53e5c754` FOREIGN KEY (`aitrainingworkflow_id`) REFERENCES `assessment_aitrainingworkflow` (`id`),
|
||||
CONSTRAINT `ff4ddecc43bd06c0d85785a61e955133` FOREIGN KEY (`trainingexample_id`) REFERENCES `assessment_trainingexample` (`id`)
|
||||
CONSTRAINT `ff4ddecc43bd06c0d85785a61e955133` FOREIGN KEY (`trainingexample_id`) REFERENCES `assessment_trainingexample` (`id`),
|
||||
CONSTRAINT `da55be90caee21d95136e40c53e5c754` FOREIGN KEY (`aitrainingworkflow_id`) REFERENCES `assessment_aitrainingworkflow` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `assessment_assessment`;
|
||||
@@ -154,8 +154,8 @@ CREATE TABLE `assessment_assessmentfeedback_assessments` (
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `assessmentfeedback_id` (`assessmentfeedback_id`,`assessment_id`),
|
||||
KEY `asses_assessment_id_392d354eca2e0c87_fk_assessment_assessment_id` (`assessment_id`),
|
||||
CONSTRAINT `D1fc3fa7cd7be79d20561668a95a9fc1` FOREIGN KEY (`assessmentfeedback_id`) REFERENCES `assessment_assessmentfeedback` (`id`),
|
||||
CONSTRAINT `asses_assessment_id_392d354eca2e0c87_fk_assessment_assessment_id` FOREIGN KEY (`assessment_id`) REFERENCES `assessment_assessment` (`id`)
|
||||
CONSTRAINT `asses_assessment_id_392d354eca2e0c87_fk_assessment_assessment_id` FOREIGN KEY (`assessment_id`) REFERENCES `assessment_assessment` (`id`),
|
||||
CONSTRAINT `D1fc3fa7cd7be79d20561668a95a9fc1` FOREIGN KEY (`assessmentfeedback_id`) REFERENCES `assessment_assessmentfeedback` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `assessment_assessmentfeedback_options`;
|
||||
@@ -168,8 +168,8 @@ CREATE TABLE `assessment_assessmentfeedback_options` (
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `assessmentfeedback_id` (`assessmentfeedback_id`,`assessmentfeedbackoption_id`),
|
||||
KEY `cc7028abc88c431df3172c9b2d6422e4` (`assessmentfeedbackoption_id`),
|
||||
CONSTRAINT `cba12ac98c4a04d67d5edaa2223f4fe5` FOREIGN KEY (`assessmentfeedback_id`) REFERENCES `assessment_assessmentfeedback` (`id`),
|
||||
CONSTRAINT `cc7028abc88c431df3172c9b2d6422e4` FOREIGN KEY (`assessmentfeedbackoption_id`) REFERENCES `assessment_assessmentfeedbackoption` (`id`)
|
||||
CONSTRAINT `cc7028abc88c431df3172c9b2d6422e4` FOREIGN KEY (`assessmentfeedbackoption_id`) REFERENCES `assessment_assessmentfeedbackoption` (`id`),
|
||||
CONSTRAINT `cba12ac98c4a04d67d5edaa2223f4fe5` FOREIGN KEY (`assessmentfeedback_id`) REFERENCES `assessment_assessmentfeedback` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `assessment_assessmentfeedbackoption`;
|
||||
@@ -196,8 +196,8 @@ CREATE TABLE `assessment_assessmentpart` (
|
||||
KEY `assessment_assessmentpart_385b00a3` (`criterion_id`),
|
||||
KEY `assessment_assessmentpart_28df3725` (`option_id`),
|
||||
CONSTRAINT `asse_option_id_2508a14feeabf4ce_fk_assessment_criterionoption_id` FOREIGN KEY (`option_id`) REFERENCES `assessment_criterionoption` (`id`),
|
||||
CONSTRAINT `asses_assessment_id_1d752290138ce479_fk_assessment_assessment_id` FOREIGN KEY (`assessment_id`) REFERENCES `assessment_assessment` (`id`),
|
||||
CONSTRAINT `assessm_criterion_id_2061f2359fd292bf_fk_assessment_criterion_id` FOREIGN KEY (`criterion_id`) REFERENCES `assessment_criterion` (`id`)
|
||||
CONSTRAINT `assessm_criterion_id_2061f2359fd292bf_fk_assessment_criterion_id` FOREIGN KEY (`criterion_id`) REFERENCES `assessment_criterion` (`id`),
|
||||
CONSTRAINT `asses_assessment_id_1d752290138ce479_fk_assessment_assessment_id` FOREIGN KEY (`assessment_id`) REFERENCES `assessment_assessment` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `assessment_criterion`;
|
||||
@@ -272,9 +272,9 @@ CREATE TABLE `assessment_peerworkflowitem` (
|
||||
KEY `assessm_scorer_id_2d803ee2d52c0e2c_fk_assessment_peerworkflow_id` (`scorer_id`),
|
||||
KEY `assessment_peerworkflowitem_ab5b2b73` (`submission_uuid`),
|
||||
KEY `assessment_peerworkflowitem_ff1ae11b` (`started_at`),
|
||||
CONSTRAINT `asses_assessment_id_15cadfae90ddcc2a_fk_assessment_assessment_id` FOREIGN KEY (`assessment_id`) REFERENCES `assessment_assessment` (`id`),
|
||||
CONSTRAINT `assessm_scorer_id_2d803ee2d52c0e2c_fk_assessment_peerworkflow_id` FOREIGN KEY (`scorer_id`) REFERENCES `assessment_peerworkflow` (`id`),
|
||||
CONSTRAINT `assessm_author_id_1948f89dea6d2b5f_fk_assessment_peerworkflow_id` FOREIGN KEY (`author_id`) REFERENCES `assessment_peerworkflow` (`id`),
|
||||
CONSTRAINT `assessm_scorer_id_2d803ee2d52c0e2c_fk_assessment_peerworkflow_id` FOREIGN KEY (`scorer_id`) REFERENCES `assessment_peerworkflow` (`id`)
|
||||
CONSTRAINT `asses_assessment_id_15cadfae90ddcc2a_fk_assessment_assessment_id` FOREIGN KEY (`assessment_id`) REFERENCES `assessment_assessment` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `assessment_rubric`;
|
||||
@@ -345,8 +345,8 @@ CREATE TABLE `assessment_studenttrainingworkflowitem` (
|
||||
UNIQUE KEY `assessment_studenttrainingwork_workflow_id_484e930feb86ad74_uniq` (`workflow_id`,`order_num`),
|
||||
KEY `assessment_studenttrainingworkflowitem_9cc97abc` (`training_example_id`),
|
||||
KEY `assessment_studenttrainingworkflowitem_846c77cf` (`workflow_id`),
|
||||
CONSTRAINT `D74ce3e30635de397fef41ac869640c7` FOREIGN KEY (`training_example_id`) REFERENCES `assessment_trainingexample` (`id`),
|
||||
CONSTRAINT `f9c080ebc7ad16394edda963ed3f280f` FOREIGN KEY (`workflow_id`) REFERENCES `assessment_studenttrainingworkflow` (`id`)
|
||||
CONSTRAINT `f9c080ebc7ad16394edda963ed3f280f` FOREIGN KEY (`workflow_id`) REFERENCES `assessment_studenttrainingworkflow` (`id`),
|
||||
CONSTRAINT `D74ce3e30635de397fef41ac869640c7` FOREIGN KEY (`training_example_id`) REFERENCES `assessment_trainingexample` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `assessment_trainingexample`;
|
||||
@@ -412,7 +412,7 @@ CREATE TABLE `auth_permission` (
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `content_type_id` (`content_type_id`,`codename`),
|
||||
CONSTRAINT `auth__content_type_id_508cf46651277a81_fk_django_content_type_id` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=746 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=755 DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `auth_registration`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
@@ -444,7 +444,7 @@ CREATE TABLE `auth_user` (
|
||||
`date_joined` datetime(6) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `username` (`username`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `auth_user_groups`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
@@ -872,6 +872,35 @@ CREATE TABLE `certificates_generatedcertificate` (
|
||||
CONSTRAINT `certificates_generatedc_user_id_77ed5f7a53121815_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `commerce_commerceconfiguration`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `commerce_commerceconfiguration` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`change_date` datetime(6) NOT NULL,
|
||||
`enabled` tinyint(1) NOT NULL,
|
||||
`checkout_on_ecommerce_service` tinyint(1) NOT NULL,
|
||||
`single_course_checkout_page` varchar(255) NOT NULL,
|
||||
`changed_by_id` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `commerce_commerce_changed_by_id_7441951d1c97c1d7_fk_auth_user_id` (`changed_by_id`),
|
||||
CONSTRAINT `commerce_commerce_changed_by_id_7441951d1c97c1d7_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 `contentserver_courseassetcachettlconfig`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `contentserver_courseassetcachettlconfig` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`change_date` datetime(6) NOT NULL,
|
||||
`enabled` tinyint(1) NOT NULL,
|
||||
`cache_ttl` int(10) unsigned NOT NULL,
|
||||
`changed_by_id` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `contentserver_cou_changed_by_id_3b5e5ff6c6df495d_fk_auth_user_id` (`changed_by_id`),
|
||||
CONSTRAINT `contentserver_cou_changed_by_id_3b5e5ff6c6df495d_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 `contentstore_pushnotificationconfig`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
@@ -945,8 +974,8 @@ CREATE TABLE `course_action_state_coursererunstate` (
|
||||
KEY `course_action_state_coursererunstate_c8235886` (`course_key`),
|
||||
KEY `course_action_state_coursererunstate_418c5509` (`action`),
|
||||
KEY `course_action_state_coursererunstate_a9bd7343` (`source_course_key`),
|
||||
CONSTRAINT `course_action_s_created_user_id_7f53088ef8dccd0b_fk_auth_user_id` FOREIGN KEY (`created_user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `course_action_s_updated_user_id_4fab18012332c9a4_fk_auth_user_id` FOREIGN KEY (`updated_user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `course_action_s_updated_user_id_4fab18012332c9a4_fk_auth_user_id` FOREIGN KEY (`updated_user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `course_action_s_created_user_id_7f53088ef8dccd0b_fk_auth_user_id` FOREIGN KEY (`created_user_id`) REFERENCES `auth_user` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `course_creators_coursecreator`;
|
||||
@@ -975,8 +1004,8 @@ CREATE TABLE `course_groups_cohortmembership` (
|
||||
UNIQUE KEY `course_groups_cohortmembership_user_id_395bddd0389ed7da_uniq` (`user_id`,`course_id`),
|
||||
KEY `course_groups_cohortmembership_6e438ee3` (`course_user_group_id`),
|
||||
KEY `course_groups_cohortmembership_e8701ad4` (`user_id`),
|
||||
CONSTRAINT `D004e77c965054d46217a8bd48bcaec8` FOREIGN KEY (`course_user_group_id`) REFERENCES `course_groups_courseusergroup` (`id`),
|
||||
CONSTRAINT `course_groups_cohortmem_user_id_15d408bf736398bf_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `course_groups_cohortmem_user_id_15d408bf736398bf_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `D004e77c965054d46217a8bd48bcaec8` FOREIGN KEY (`course_user_group_id`) REFERENCES `course_groups_courseusergroup` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `course_groups_coursecohort`;
|
||||
@@ -1114,7 +1143,6 @@ CREATE TABLE `course_overviews_courseoverview` (
|
||||
`end` datetime(6) DEFAULT NULL,
|
||||
`advertised_start` longtext,
|
||||
`course_image_url` longtext NOT NULL,
|
||||
`facebook_url` longtext,
|
||||
`social_sharing_url` longtext,
|
||||
`end_of_course_survey_url` longtext,
|
||||
`certificates_display_behavior` longtext,
|
||||
@@ -1139,6 +1167,7 @@ CREATE TABLE `course_overviews_courseoverview` (
|
||||
`effort` longtext,
|
||||
`short_description` longtext,
|
||||
`org` longtext NOT NULL,
|
||||
`facebook_url` longtext,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
@@ -1558,8 +1587,8 @@ CREATE TABLE `django_admin_log` (
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `djang_content_type_id_697914295151027a_fk_django_content_type_id` (`content_type_id`),
|
||||
KEY `django_admin_log_user_id_52fdd58701c5f563_fk_auth_user_id` (`user_id`),
|
||||
CONSTRAINT `djang_content_type_id_697914295151027a_fk_django_content_type_id` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`),
|
||||
CONSTRAINT `django_admin_log_user_id_52fdd58701c5f563_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `django_admin_log_user_id_52fdd58701c5f563_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `djang_content_type_id_697914295151027a_fk_django_content_type_id` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `django_comment_client_permission`;
|
||||
@@ -1580,8 +1609,8 @@ CREATE TABLE `django_comment_client_permission_roles` (
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `permission_id` (`permission_id`,`role_id`),
|
||||
KEY `django_role_id_558412c96ef7ba87_fk_django_comment_client_role_id` (`role_id`),
|
||||
CONSTRAINT `D4e9a4067c1db9041491363f5e032121` FOREIGN KEY (`permission_id`) REFERENCES `django_comment_client_permission` (`name`),
|
||||
CONSTRAINT `django_role_id_558412c96ef7ba87_fk_django_comment_client_role_id` FOREIGN KEY (`role_id`) REFERENCES `django_comment_client_role` (`id`)
|
||||
CONSTRAINT `django_role_id_558412c96ef7ba87_fk_django_comment_client_role_id` FOREIGN KEY (`role_id`) REFERENCES `django_comment_client_role` (`id`),
|
||||
CONSTRAINT `D4e9a4067c1db9041491363f5e032121` FOREIGN KEY (`permission_id`) REFERENCES `django_comment_client_permission` (`name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `django_comment_client_role`;
|
||||
@@ -1618,7 +1647,7 @@ CREATE TABLE `django_content_type` (
|
||||
`model` varchar(100) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `django_content_type_app_label_45f3b1d93ec8c61c_uniq` (`app_label`,`model`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=248 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=251 DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `django_migrations`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
@@ -1629,7 +1658,7 @@ CREATE TABLE `django_migrations` (
|
||||
`name` varchar(255) NOT NULL,
|
||||
`applied` datetime(6) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=107 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=119 DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `django_openid_auth_association`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
@@ -1737,8 +1766,8 @@ CREATE TABLE `djcelery_periodictask` (
|
||||
UNIQUE KEY `name` (`name`),
|
||||
KEY `djc_interval_id_20cfc1cad060dfad_fk_djcelery_intervalschedule_id` (`interval_id`),
|
||||
KEY `djcel_crontab_id_1d8228f5b44b680a_fk_djcelery_crontabschedule_id` (`crontab_id`),
|
||||
CONSTRAINT `djc_interval_id_20cfc1cad060dfad_fk_djcelery_intervalschedule_id` FOREIGN KEY (`interval_id`) REFERENCES `djcelery_intervalschedule` (`id`),
|
||||
CONSTRAINT `djcel_crontab_id_1d8228f5b44b680a_fk_djcelery_crontabschedule_id` FOREIGN KEY (`crontab_id`) REFERENCES `djcelery_crontabschedule` (`id`)
|
||||
CONSTRAINT `djcel_crontab_id_1d8228f5b44b680a_fk_djcelery_crontabschedule_id` FOREIGN KEY (`crontab_id`) REFERENCES `djcelery_crontabschedule` (`id`),
|
||||
CONSTRAINT `djc_interval_id_20cfc1cad060dfad_fk_djcelery_intervalschedule_id` FOREIGN KEY (`interval_id`) REFERENCES `djcelery_intervalschedule` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `djcelery_periodictasks`;
|
||||
@@ -1819,8 +1848,8 @@ CREATE TABLE `edxval_encodedvideo` (
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `edxval_encodedvideo_83a0eb3f` (`profile_id`),
|
||||
KEY `edxval_encodedvideo_b58b747e` (`video_id`),
|
||||
CONSTRAINT `edxval_encodedv_profile_id_484a111092acafb3_fk_edxval_profile_id` FOREIGN KEY (`profile_id`) REFERENCES `edxval_profile` (`id`),
|
||||
CONSTRAINT `edxval_encodedvideo_video_id_56934bca09fc3b13_fk_edxval_video_id` FOREIGN KEY (`video_id`) REFERENCES `edxval_video` (`id`)
|
||||
CONSTRAINT `edxval_encodedvideo_video_id_56934bca09fc3b13_fk_edxval_video_id` FOREIGN KEY (`video_id`) REFERENCES `edxval_video` (`id`),
|
||||
CONSTRAINT `edxval_encodedv_profile_id_484a111092acafb3_fk_edxval_profile_id` FOREIGN KEY (`profile_id`) REFERENCES `edxval_profile` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `edxval_profile`;
|
||||
@@ -2087,8 +2116,7 @@ CREATE TABLE `microsite_configuration_micrositehistory` (
|
||||
`values` longtext NOT NULL,
|
||||
`site_id` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `key` (`key`),
|
||||
UNIQUE KEY `site_id` (`site_id`),
|
||||
KEY `microsite_configurati_site_id_6977a04d3625a533_fk_django_site_id` (`site_id`),
|
||||
CONSTRAINT `microsite_configurati_site_id_6977a04d3625a533_fk_django_site_id` FOREIGN KEY (`site_id`) REFERENCES `django_site` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
@@ -2131,12 +2159,14 @@ CREATE TABLE `milestones_coursecontentmilestone` (
|
||||
`active` tinyint(1) NOT NULL,
|
||||
`milestone_id` int(11) NOT NULL,
|
||||
`milestone_relationship_type_id` int(11) NOT NULL,
|
||||
`requirements` varchar(255),
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `milestones_coursecontentmileston_course_id_68d1457cd52d6dff_uniq` (`course_id`,`content_id`,`milestone_id`),
|
||||
KEY `milestones_coursecontentmilestone_ea134da7` (`course_id`),
|
||||
KEY `milestones_coursecontentmilestone_e14f02ad` (`content_id`),
|
||||
KEY `milestones_coursecontentmilestone_dbb5cd1e` (`milestone_id`),
|
||||
KEY `milestones_coursecontentmilestone_db6866e3` (`milestone_relationship_type_id`),
|
||||
KEY `milestones_coursecontentmilestone_active_39b5c645fa33bfee_uniq` (`active`),
|
||||
CONSTRAINT `D84e404851bc6d6b9fe0d60955e8729c` FOREIGN KEY (`milestone_relationship_type_id`) REFERENCES `milestones_milestonerelationshiptype` (`id`),
|
||||
CONSTRAINT `milesto_milestone_id_73b6eddde5b205a8_fk_milestones_milestone_id` FOREIGN KEY (`milestone_id`) REFERENCES `milestones_milestone` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
@@ -2157,6 +2187,7 @@ CREATE TABLE `milestones_coursemilestone` (
|
||||
KEY `milestones_coursemilestone_ea134da7` (`course_id`),
|
||||
KEY `milestones_coursemilestone_dbb5cd1e` (`milestone_id`),
|
||||
KEY `milestones_coursemilestone_db6866e3` (`milestone_relationship_type_id`),
|
||||
KEY `milestones_coursemilestone_active_5c3a925f8cc4bde2_uniq` (`active`),
|
||||
CONSTRAINT `D69536d0d313008147c5daf5341090e1` FOREIGN KEY (`milestone_relationship_type_id`) REFERENCES `milestones_milestonerelationshiptype` (`id`),
|
||||
CONSTRAINT `milesto_milestone_id_284153799c54d7d8_fk_milestones_milestone_id` FOREIGN KEY (`milestone_id`) REFERENCES `milestones_milestone` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
@@ -2176,7 +2207,8 @@ CREATE TABLE `milestones_milestone` (
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `milestones_milestone_namespace_460a2f6943016c0b_uniq` (`namespace`,`name`),
|
||||
KEY `milestones_milestone_89801e9e` (`namespace`),
|
||||
KEY `milestones_milestone_b068931c` (`name`)
|
||||
KEY `milestones_milestone_b068931c` (`name`),
|
||||
KEY `milestones_milestone_active_1182ba3c09d42c35_uniq` (`active`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `milestones_milestonerelationshiptype`;
|
||||
@@ -2209,6 +2241,7 @@ CREATE TABLE `milestones_usermilestone` (
|
||||
UNIQUE KEY `milestones_usermilestone_user_id_10206aa452468351_uniq` (`user_id`,`milestone_id`),
|
||||
KEY `milesto_milestone_id_4fe38e3e9994f15c_fk_milestones_milestone_id` (`milestone_id`),
|
||||
KEY `milestones_usermilestone_e8701ad4` (`user_id`),
|
||||
KEY `milestones_usermilestone_active_1827f467fe87a8ea_uniq` (`active`),
|
||||
CONSTRAINT `milesto_milestone_id_4fe38e3e9994f15c_fk_milestones_milestone_id` FOREIGN KEY (`milestone_id`) REFERENCES `milestones_milestone` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
@@ -2304,8 +2337,8 @@ CREATE TABLE `notify_subscription` (
|
||||
PRIMARY KEY (`subscription_id`),
|
||||
KEY `a2462650bbefc26547210b80dec61069` (`notification_type_id`),
|
||||
KEY `notify_subscr_settings_id_64d594d127e8ca95_fk_notify_settings_id` (`settings_id`),
|
||||
CONSTRAINT `a2462650bbefc26547210b80dec61069` FOREIGN KEY (`notification_type_id`) REFERENCES `notify_notificationtype` (`key`),
|
||||
CONSTRAINT `notify_subscr_settings_id_64d594d127e8ca95_fk_notify_settings_id` FOREIGN KEY (`settings_id`) REFERENCES `notify_settings` (`id`)
|
||||
CONSTRAINT `notify_subscr_settings_id_64d594d127e8ca95_fk_notify_settings_id` FOREIGN KEY (`settings_id`) REFERENCES `notify_settings` (`id`),
|
||||
CONSTRAINT `a2462650bbefc26547210b80dec61069` FOREIGN KEY (`notification_type_id`) REFERENCES `notify_notificationtype` (`key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `oauth2_accesstoken`;
|
||||
@@ -2322,8 +2355,8 @@ CREATE TABLE `oauth2_accesstoken` (
|
||||
KEY `oauth2_accesstoken_94a08da1` (`token`),
|
||||
KEY `oauth2_accesstoken_2bfe9d72` (`client_id`),
|
||||
KEY `oauth2_accesstoken_e8701ad4` (`user_id`),
|
||||
CONSTRAINT `oauth2_accesstoke_client_id_20c73b03a7c139a2_fk_oauth2_client_id` FOREIGN KEY (`client_id`) REFERENCES `oauth2_client` (`id`),
|
||||
CONSTRAINT `oauth2_accesstoken_user_id_7a865c7085722378_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `oauth2_accesstoken_user_id_7a865c7085722378_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `oauth2_accesstoke_client_id_20c73b03a7c139a2_fk_oauth2_client_id` FOREIGN KEY (`client_id`) REFERENCES `oauth2_client` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `oauth2_client`;
|
||||
@@ -2357,8 +2390,8 @@ CREATE TABLE `oauth2_grant` (
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `oauth2_grant_client_id_fbfc174fbc856af_fk_oauth2_client_id` (`client_id`),
|
||||
KEY `oauth2_grant_user_id_3de96a461bb76819_fk_auth_user_id` (`user_id`),
|
||||
CONSTRAINT `oauth2_grant_client_id_fbfc174fbc856af_fk_oauth2_client_id` FOREIGN KEY (`client_id`) REFERENCES `oauth2_client` (`id`),
|
||||
CONSTRAINT `oauth2_grant_user_id_3de96a461bb76819_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `oauth2_grant_user_id_3de96a461bb76819_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `oauth2_grant_client_id_fbfc174fbc856af_fk_oauth2_client_id` FOREIGN KEY (`client_id`) REFERENCES `oauth2_client` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `oauth2_provider_trustedclient`;
|
||||
@@ -2386,9 +2419,9 @@ CREATE TABLE `oauth2_refreshtoken` (
|
||||
UNIQUE KEY `access_token_id` (`access_token_id`),
|
||||
KEY `oauth2_refreshtok_client_id_2f55036ac9aa614e_fk_oauth2_client_id` (`client_id`),
|
||||
KEY `oauth2_refreshtoken_user_id_acecf94460b787c_fk_auth_user_id` (`user_id`),
|
||||
CONSTRAINT `oauth2__access_token_id_f99377d503a000b_fk_oauth2_accesstoken_id` FOREIGN KEY (`access_token_id`) REFERENCES `oauth2_accesstoken` (`id`),
|
||||
CONSTRAINT `oauth2_refreshtoken_user_id_acecf94460b787c_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `oauth2_refreshtok_client_id_2f55036ac9aa614e_fk_oauth2_client_id` FOREIGN KEY (`client_id`) REFERENCES `oauth2_client` (`id`),
|
||||
CONSTRAINT `oauth2_refreshtoken_user_id_acecf94460b787c_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `oauth2__access_token_id_f99377d503a000b_fk_oauth2_accesstoken_id` FOREIGN KEY (`access_token_id`) REFERENCES `oauth2_accesstoken` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `oauth_provider_consumer`;
|
||||
@@ -2452,9 +2485,9 @@ CREATE TABLE `oauth_provider_token` (
|
||||
KEY `oauth_consumer_id_1b9915b5bcf1ee5b_fk_oauth_provider_consumer_id` (`consumer_id`),
|
||||
KEY `oauth_provi_scope_id_459821b6fecbc02a_fk_oauth_provider_scope_id` (`scope_id`),
|
||||
KEY `oauth_provider_token_user_id_588adbcffc892186_fk_auth_user_id` (`user_id`),
|
||||
CONSTRAINT `oauth_provider_token_user_id_588adbcffc892186_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `oauth_consumer_id_1b9915b5bcf1ee5b_fk_oauth_provider_consumer_id` FOREIGN KEY (`consumer_id`) REFERENCES `oauth_provider_consumer` (`id`),
|
||||
CONSTRAINT `oauth_provi_scope_id_459821b6fecbc02a_fk_oauth_provider_scope_id` FOREIGN KEY (`scope_id`) REFERENCES `oauth_provider_scope` (`id`),
|
||||
CONSTRAINT `oauth_provider_token_user_id_588adbcffc892186_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `oauth_provi_scope_id_459821b6fecbc02a_fk_oauth_provider_scope_id` FOREIGN KEY (`scope_id`) REFERENCES `oauth_provider_scope` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `organizations_organization`;
|
||||
@@ -2491,24 +2524,6 @@ CREATE TABLE `organizations_organizationcourse` (
|
||||
CONSTRAINT `a7b04b16eba98e518fbe21d390bd8e3e` FOREIGN KEY (`organization_id`) REFERENCES `organizations_organization` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `problem_builder_answer`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `problem_builder_answer` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(50) NOT NULL,
|
||||
`student_id` varchar(32) NOT NULL,
|
||||
`course_id` varchar(50) NOT NULL,
|
||||
`student_input` longtext NOT NULL,
|
||||
`created_on` datetime(6) NOT NULL,
|
||||
`modified_on` datetime(6) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `problem_builder_answer_student_id_2f6847a9fb3e9385_uniq` (`student_id`,`course_id`,`name`),
|
||||
KEY `problem_builder_answer_b068931c` (`name`),
|
||||
KEY `problem_builder_answer_30a811f6` (`student_id`),
|
||||
KEY `problem_builder_answer_ea134da7` (`course_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `proctoring_proctoredexam`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
@@ -2545,8 +2560,8 @@ CREATE TABLE `proctoring_proctoredexamreviewpolicy` (
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `D32bab97500954b362d3f768dd89b6da` (`proctored_exam_id`),
|
||||
KEY `proctoring_proct_set_by_user_id_75a66580aa44cd84_fk_auth_user_id` (`set_by_user_id`),
|
||||
CONSTRAINT `D32bab97500954b362d3f768dd89b6da` FOREIGN KEY (`proctored_exam_id`) REFERENCES `proctoring_proctoredexam` (`id`),
|
||||
CONSTRAINT `proctoring_proct_set_by_user_id_75a66580aa44cd84_fk_auth_user_id` FOREIGN KEY (`set_by_user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `proctoring_proct_set_by_user_id_75a66580aa44cd84_fk_auth_user_id` FOREIGN KEY (`set_by_user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `D32bab97500954b362d3f768dd89b6da` FOREIGN KEY (`proctored_exam_id`) REFERENCES `proctoring_proctoredexam` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `proctoring_proctoredexamreviewpolicyhistory`;
|
||||
@@ -2564,8 +2579,8 @@ CREATE TABLE `proctoring_proctoredexamreviewpolicyhistory` (
|
||||
KEY `d9965d8af87bebd0587414ca1ba4826f` (`proctored_exam_id`),
|
||||
KEY `proctoring_procto_set_by_user_id_31fae610848d90f_fk_auth_user_id` (`set_by_user_id`),
|
||||
KEY `proctoring_proctoredexamreviewpolicyhistory_524b09d0` (`original_id`),
|
||||
CONSTRAINT `d9965d8af87bebd0587414ca1ba4826f` FOREIGN KEY (`proctored_exam_id`) REFERENCES `proctoring_proctoredexam` (`id`),
|
||||
CONSTRAINT `proctoring_procto_set_by_user_id_31fae610848d90f_fk_auth_user_id` FOREIGN KEY (`set_by_user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `proctoring_procto_set_by_user_id_31fae610848d90f_fk_auth_user_id` FOREIGN KEY (`set_by_user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `d9965d8af87bebd0587414ca1ba4826f` FOREIGN KEY (`proctored_exam_id`) REFERENCES `proctoring_proctoredexam` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `proctoring_proctoredexamsoftwaresecurereview`;
|
||||
@@ -2583,13 +2598,14 @@ CREATE TABLE `proctoring_proctoredexamsoftwaresecurereview` (
|
||||
`reviewed_by_id` int(11) DEFAULT NULL,
|
||||
`student_id` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `proctoring_proctoredexamsoftw_attempt_code_69b9866a54964afb_uniq` (`attempt_code`),
|
||||
KEY `proctori_exam_id_635059f5fe2cc392_fk_proctoring_proctoredexam_id` (`exam_id`),
|
||||
KEY `proctoring_proct_reviewed_by_id_4cff67b7de094f65_fk_auth_user_id` (`reviewed_by_id`),
|
||||
KEY `proctoring_proctored_student_id_14c182517b0cbb5b_fk_auth_user_id` (`student_id`),
|
||||
KEY `proctoring_proctoredexamsoftwaresecurereview_b38e5b0e` (`attempt_code`),
|
||||
CONSTRAINT `proctori_exam_id_635059f5fe2cc392_fk_proctoring_proctoredexam_id` FOREIGN KEY (`exam_id`) REFERENCES `proctoring_proctoredexam` (`id`),
|
||||
CONSTRAINT `proctoring_proctored_student_id_14c182517b0cbb5b_fk_auth_user_id` FOREIGN KEY (`student_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `proctoring_proct_reviewed_by_id_4cff67b7de094f65_fk_auth_user_id` FOREIGN KEY (`reviewed_by_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `proctoring_proctored_student_id_14c182517b0cbb5b_fk_auth_user_id` FOREIGN KEY (`student_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `proctori_exam_id_635059f5fe2cc392_fk_proctoring_proctoredexam_id` FOREIGN KEY (`exam_id`) REFERENCES `proctoring_proctoredexam` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `proctoring_proctoredexamsoftwaresecurereviewhistory`;
|
||||
@@ -2611,9 +2627,9 @@ CREATE TABLE `proctoring_proctoredexamsoftwaresecurereviewhistory` (
|
||||
KEY `proctoring_proct_reviewed_by_id_139568d0bf423998_fk_auth_user_id` (`reviewed_by_id`),
|
||||
KEY `proctoring_proctored_student_id_6922ba3b791462d8_fk_auth_user_id` (`student_id`),
|
||||
KEY `proctoring_proctoredexamsoftwaresecurereviewhistory_b38e5b0e` (`attempt_code`),
|
||||
CONSTRAINT `proctori_exam_id_73969ae423813477_fk_proctoring_proctoredexam_id` FOREIGN KEY (`exam_id`) REFERENCES `proctoring_proctoredexam` (`id`),
|
||||
CONSTRAINT `proctoring_proctored_student_id_6922ba3b791462d8_fk_auth_user_id` FOREIGN KEY (`student_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `proctoring_proct_reviewed_by_id_139568d0bf423998_fk_auth_user_id` FOREIGN KEY (`reviewed_by_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `proctoring_proctored_student_id_6922ba3b791462d8_fk_auth_user_id` FOREIGN KEY (`student_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `proctori_exam_id_73969ae423813477_fk_proctoring_proctoredexam_id` FOREIGN KEY (`exam_id`) REFERENCES `proctoring_proctoredexam` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `proctoring_proctoredexamstudentallowance`;
|
||||
@@ -2630,8 +2646,8 @@ CREATE TABLE `proctoring_proctoredexamstudentallowance` (
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `proctoring_proctoredexamstudentall_user_id_665ed945152c2f60_uniq` (`user_id`,`proctored_exam_id`,`key`),
|
||||
KEY `db55b83a7875e70b3a0ebd1f81a898d8` (`proctored_exam_id`),
|
||||
CONSTRAINT `db55b83a7875e70b3a0ebd1f81a898d8` FOREIGN KEY (`proctored_exam_id`) REFERENCES `proctoring_proctoredexam` (`id`),
|
||||
CONSTRAINT `proctoring_proctoredexam_user_id_a0a0681d4a01661_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `proctoring_proctoredexam_user_id_a0a0681d4a01661_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `db55b83a7875e70b3a0ebd1f81a898d8` FOREIGN KEY (`proctored_exam_id`) REFERENCES `proctoring_proctoredexam` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `proctoring_proctoredexamstudentallowancehistory`;
|
||||
@@ -2649,8 +2665,8 @@ CREATE TABLE `proctoring_proctoredexamstudentallowancehistory` (
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `D169ec97a7fca1dbf6b0bb2929d41ccc` (`proctored_exam_id`),
|
||||
KEY `proctoring_proctoredexa_user_id_68e25e3abb187580_fk_auth_user_id` (`user_id`),
|
||||
CONSTRAINT `D169ec97a7fca1dbf6b0bb2929d41ccc` FOREIGN KEY (`proctored_exam_id`) REFERENCES `proctoring_proctoredexam` (`id`),
|
||||
CONSTRAINT `proctoring_proctoredexa_user_id_68e25e3abb187580_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `proctoring_proctoredexa_user_id_68e25e3abb187580_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `D169ec97a7fca1dbf6b0bb2929d41ccc` FOREIGN KEY (`proctored_exam_id`) REFERENCES `proctoring_proctoredexam` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `proctoring_proctoredexamstudentattempt`;
|
||||
@@ -2729,8 +2745,8 @@ CREATE TABLE `proctoring_proctoredexamstudentattempthistory` (
|
||||
KEY `proctoring_proctoredexa_user_id_59ce75db7c4fc769_fk_auth_user_id` (`user_id`),
|
||||
KEY `proctoring_proctoredexamstudentattempthistory_b38e5b0e` (`attempt_code`),
|
||||
KEY `proctoring_proctoredexamstudentattempthistory_0e684294` (`external_id`),
|
||||
CONSTRAINT `cbccbfd5c4c427541fdce96e77e6bf6c` FOREIGN KEY (`proctored_exam_id`) REFERENCES `proctoring_proctoredexam` (`id`),
|
||||
CONSTRAINT `proctoring_proctoredexa_user_id_59ce75db7c4fc769_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `proctoring_proctoredexa_user_id_59ce75db7c4fc769_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `cbccbfd5c4c427541fdce96e77e6bf6c` FOREIGN KEY (`proctored_exam_id`) REFERENCES `proctoring_proctoredexam` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `programs_programsapiconfig`;
|
||||
@@ -2749,6 +2765,8 @@ CREATE TABLE `programs_programsapiconfig` (
|
||||
`authoring_app_css_path` varchar(255) NOT NULL,
|
||||
`authoring_app_js_path` varchar(255) NOT NULL,
|
||||
`enable_studio_tab` tinyint(1) NOT NULL,
|
||||
`enable_certification` tinyint(1) NOT NULL,
|
||||
`max_retries` int(10) unsigned NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `programs_programsa_changed_by_id_b7c3b49d5c0dcd3_fk_auth_user_id` (`changed_by_id`),
|
||||
CONSTRAINT `programs_programsa_changed_by_id_b7c3b49d5c0dcd3_fk_auth_user_id` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`)
|
||||
@@ -2827,9 +2845,9 @@ CREATE TABLE `shoppingcart_couponredemption` (
|
||||
KEY `shoppingcar_coupon_id_1afa016627ac44bb_fk_shoppingcart_coupon_id` (`coupon_id`),
|
||||
KEY `shoppingcart_couponredemption_69dfcb07` (`order_id`),
|
||||
KEY `shoppingcart_couponredemption_e8701ad4` (`user_id`),
|
||||
CONSTRAINT `shoppingcar_coupon_id_1afa016627ac44bb_fk_shoppingcart_coupon_id` FOREIGN KEY (`coupon_id`) REFERENCES `shoppingcart_coupon` (`id`),
|
||||
CONSTRAINT `shoppingcart_couponredemp_user_id_f5b814b7d92666_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `shoppingcart__order_id_5ba031c3bfaf643a_fk_shoppingcart_order_id` FOREIGN KEY (`order_id`) REFERENCES `shoppingcart_order` (`id`),
|
||||
CONSTRAINT `shoppingcart_couponredemp_user_id_f5b814b7d92666_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `shoppingcar_coupon_id_1afa016627ac44bb_fk_shoppingcart_coupon_id` FOREIGN KEY (`coupon_id`) REFERENCES `shoppingcart_coupon` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `shoppingcart_courseregcodeitem`;
|
||||
@@ -2878,9 +2896,9 @@ CREATE TABLE `shoppingcart_courseregistrationcode` (
|
||||
KEY `shoppingcart_courseregistrationcode_69dfcb07` (`order_id`),
|
||||
KEY `shoppingcart_courseregistrationcode_7a471658` (`invoice_item_id`),
|
||||
CONSTRAINT `f040030b6361304bd87eb40c09a82094` FOREIGN KEY (`invoice_item_id`) REFERENCES `shoppingcart_courseregistrationcodeinvoiceitem` (`invoiceitem_ptr_id`),
|
||||
CONSTRAINT `shoppingc_invoice_id_422f26bdc7c5cb99_fk_shoppingcart_invoice_id` FOREIGN KEY (`invoice_id`) REFERENCES `shoppingcart_invoice` (`id`),
|
||||
CONSTRAINT `shoppingcart_cour_created_by_id_11125a9667aa01c9_fk_auth_user_id` FOREIGN KEY (`created_by_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `shoppingcart__order_id_279d7e2df3fe6b6a_fk_shoppingcart_order_id` FOREIGN KEY (`order_id`) REFERENCES `shoppingcart_order` (`id`),
|
||||
CONSTRAINT `shoppingcart_cour_created_by_id_11125a9667aa01c9_fk_auth_user_id` FOREIGN KEY (`created_by_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `shoppingc_invoice_id_422f26bdc7c5cb99_fk_shoppingcart_invoice_id` FOREIGN KEY (`invoice_id`) REFERENCES `shoppingcart_invoice` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `shoppingcart_courseregistrationcodeinvoiceitem`;
|
||||
@@ -2996,9 +3014,9 @@ CREATE TABLE `shoppingcart_invoicetransaction` (
|
||||
KEY `shoppingcart_invoi_created_by_id_f5f3d90ce55a145_fk_auth_user_id` (`created_by_id`),
|
||||
KEY `shoppingc_invoice_id_66bdbfa6f029288b_fk_shoppingcart_invoice_id` (`invoice_id`),
|
||||
KEY `shoppingcar_last_modified_by_id_5e10e433f9576d91_fk_auth_user_id` (`last_modified_by_id`),
|
||||
CONSTRAINT `shoppingc_invoice_id_66bdbfa6f029288b_fk_shoppingcart_invoice_id` FOREIGN KEY (`invoice_id`) REFERENCES `shoppingcart_invoice` (`id`),
|
||||
CONSTRAINT `shoppingcar_last_modified_by_id_5e10e433f9576d91_fk_auth_user_id` FOREIGN KEY (`last_modified_by_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `shoppingcart_invoi_created_by_id_f5f3d90ce55a145_fk_auth_user_id` FOREIGN KEY (`created_by_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `shoppingcart_invoi_created_by_id_f5f3d90ce55a145_fk_auth_user_id` FOREIGN KEY (`created_by_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `shoppingc_invoice_id_66bdbfa6f029288b_fk_shoppingcart_invoice_id` FOREIGN KEY (`invoice_id`) REFERENCES `shoppingcart_invoice` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `shoppingcart_order`;
|
||||
@@ -3059,8 +3077,8 @@ CREATE TABLE `shoppingcart_orderitem` (
|
||||
KEY `shoppingcart_orderitem_76ed2946` (`refund_requested_time`),
|
||||
KEY `shoppingcart_orderitem_69dfcb07` (`order_id`),
|
||||
KEY `shoppingcart_orderitem_e8701ad4` (`user_id`),
|
||||
CONSTRAINT `shoppingcart__order_id_325e5347f18743e3_fk_shoppingcart_order_id` FOREIGN KEY (`order_id`) REFERENCES `shoppingcart_order` (`id`),
|
||||
CONSTRAINT `shoppingcart_orderitem_user_id_5708ec7aabe24a31_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `shoppingcart_orderitem_user_id_5708ec7aabe24a31_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `shoppingcart__order_id_325e5347f18743e3_fk_shoppingcart_order_id` FOREIGN KEY (`order_id`) REFERENCES `shoppingcart_order` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `shoppingcart_paidcourseregistration`;
|
||||
@@ -3107,8 +3125,8 @@ CREATE TABLE `shoppingcart_registrationcoderedemption` (
|
||||
KEY `D1ed44c4be114e424571929bce972f54` (`registration_code_id`),
|
||||
CONSTRAINT `D1ed44c4be114e424571929bce972f54` FOREIGN KEY (`registration_code_id`) REFERENCES `shoppingcart_courseregistrationcode` (`id`),
|
||||
CONSTRAINT `D6654a8efe686d45804b6116dfc6bee1` FOREIGN KEY (`course_enrollment_id`) REFERENCES `student_courseenrollment` (`id`),
|
||||
CONSTRAINT `shoppingcart_r_order_id_752ddc3003afe96_fk_shoppingcart_order_id` FOREIGN KEY (`order_id`) REFERENCES `shoppingcart_order` (`id`),
|
||||
CONSTRAINT `shoppingcart_reg_redeemed_by_id_455df2dd74004fff_fk_auth_user_id` FOREIGN KEY (`redeemed_by_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `shoppingcart_reg_redeemed_by_id_455df2dd74004fff_fk_auth_user_id` FOREIGN KEY (`redeemed_by_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `shoppingcart_r_order_id_752ddc3003afe96_fk_shoppingcart_order_id` FOREIGN KEY (`order_id`) REFERENCES `shoppingcart_order` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `social_auth_association`;
|
||||
@@ -3197,6 +3215,20 @@ CREATE TABLE `static_replace_assetbaseurlconfig` (
|
||||
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 `static_replace_assetexcludedextensionsconfig`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `static_replace_assetexcludedextensionsconfig` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`change_date` datetime(6) NOT NULL,
|
||||
`enabled` tinyint(1) NOT NULL,
|
||||
`excluded_extensions` longtext NOT NULL,
|
||||
`changed_by_id` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `static_replace_as_changed_by_id_5885827de4f271dc_fk_auth_user_id` (`changed_by_id`),
|
||||
CONSTRAINT `static_replace_as_changed_by_id_5885827de4f271dc_fk_auth_user_id` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `status_coursemessage`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
@@ -3499,8 +3531,8 @@ CREATE TABLE `student_userstanding` (
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `user_id` (`user_id`),
|
||||
KEY `student_userstand_changed_by_id_23784b83f2849aff_fk_auth_user_id` (`changed_by_id`),
|
||||
CONSTRAINT `student_userstand_changed_by_id_23784b83f2849aff_fk_auth_user_id` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `student_userstanding_user_id_6bb90abaaa05d42e_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `student_userstanding_user_id_6bb90abaaa05d42e_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `student_userstand_changed_by_id_23784b83f2849aff_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 `student_usertestgroup`;
|
||||
@@ -3524,8 +3556,8 @@ CREATE TABLE `student_usertestgroup_users` (
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `usertestgroup_id` (`usertestgroup_id`,`user_id`),
|
||||
KEY `student_usertestgroup_u_user_id_26c886de60cceacb_fk_auth_user_id` (`user_id`),
|
||||
CONSTRAINT `st_usertestgroup_id_3d634741f1dd4e4f_fk_student_usertestgroup_id` FOREIGN KEY (`usertestgroup_id`) REFERENCES `student_usertestgroup` (`id`),
|
||||
CONSTRAINT `student_usertestgroup_u_user_id_26c886de60cceacb_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `student_usertestgroup_u_user_id_26c886de60cceacb_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `st_usertestgroup_id_3d634741f1dd4e4f_fk_student_usertestgroup_id` FOREIGN KEY (`usertestgroup_id`) REFERENCES `student_usertestgroup` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `submissions_score`;
|
||||
@@ -3543,8 +3575,8 @@ CREATE TABLE `submissions_score` (
|
||||
KEY `submissions_score_fde81f11` (`created_at`),
|
||||
KEY `submissions_score_02d5e83e` (`student_item_id`),
|
||||
KEY `submissions_score_1dd9cfcc` (`submission_id`),
|
||||
CONSTRAINT `s_student_item_id_7d4d4bb6a7dd0642_fk_submissions_studentitem_id` FOREIGN KEY (`student_item_id`) REFERENCES `submissions_studentitem` (`id`),
|
||||
CONSTRAINT `subm_submission_id_3fc975fe88442ff7_fk_submissions_submission_id` FOREIGN KEY (`submission_id`) REFERENCES `submissions_submission` (`id`)
|
||||
CONSTRAINT `subm_submission_id_3fc975fe88442ff7_fk_submissions_submission_id` FOREIGN KEY (`submission_id`) REFERENCES `submissions_submission` (`id`),
|
||||
CONSTRAINT `s_student_item_id_7d4d4bb6a7dd0642_fk_submissions_studentitem_id` FOREIGN KEY (`student_item_id`) REFERENCES `submissions_studentitem` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `submissions_scoreannotation`;
|
||||
@@ -3576,8 +3608,8 @@ CREATE TABLE `submissions_scoresummary` (
|
||||
KEY `submissions__highest_id_7fd91b8eb312c175_fk_submissions_score_id` (`highest_id`),
|
||||
KEY `submissions_s_latest_id_2b352506a35fd569_fk_submissions_score_id` (`latest_id`),
|
||||
CONSTRAINT `s_student_item_id_32fa0a425a149b1b_fk_submissions_studentitem_id` FOREIGN KEY (`student_item_id`) REFERENCES `submissions_studentitem` (`id`),
|
||||
CONSTRAINT `submissions__highest_id_7fd91b8eb312c175_fk_submissions_score_id` FOREIGN KEY (`highest_id`) REFERENCES `submissions_score` (`id`),
|
||||
CONSTRAINT `submissions_s_latest_id_2b352506a35fd569_fk_submissions_score_id` FOREIGN KEY (`latest_id`) REFERENCES `submissions_score` (`id`)
|
||||
CONSTRAINT `submissions_s_latest_id_2b352506a35fd569_fk_submissions_score_id` FOREIGN KEY (`latest_id`) REFERENCES `submissions_score` (`id`),
|
||||
CONSTRAINT `submissions__highest_id_7fd91b8eb312c175_fk_submissions_score_id` FOREIGN KEY (`highest_id`) REFERENCES `submissions_score` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `submissions_studentitem`;
|
||||
@@ -3607,6 +3639,7 @@ CREATE TABLE `submissions_submission` (
|
||||
`created_at` datetime(6) NOT NULL,
|
||||
`raw_answer` longtext NOT NULL,
|
||||
`student_item_id` int(11) NOT NULL,
|
||||
`status` varchar(1) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `su_student_item_id_d3801ff833d05b1_fk_submissions_studentitem_id` (`student_item_id`),
|
||||
KEY `submissions_submission_ef7c876f` (`uuid`),
|
||||
@@ -3632,8 +3665,8 @@ CREATE TABLE `survey_surveyanswer` (
|
||||
KEY `survey_surveyanswer_c8235886` (`course_key`),
|
||||
KEY `survey_surveyanswer_d6cba1ad` (`form_id`),
|
||||
KEY `survey_surveyanswer_e8701ad4` (`user_id`),
|
||||
CONSTRAINT `survey_surveyan_form_id_1c835afe12a54912_fk_survey_surveyform_id` FOREIGN KEY (`form_id`) REFERENCES `survey_surveyform` (`id`),
|
||||
CONSTRAINT `survey_surveyanswer_user_id_4e77d83a82fd0b2b_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `survey_surveyanswer_user_id_4e77d83a82fd0b2b_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `survey_surveyan_form_id_1c835afe12a54912_fk_survey_surveyform_id` FOREIGN KEY (`form_id`) REFERENCES `survey_surveyform` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `survey_surveyform`;
|
||||
@@ -3687,8 +3720,8 @@ CREATE TABLE `teams_courseteammembership` (
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `teams_courseteammembership_user_id_48efa8e8971947c3_uniq` (`user_id`,`team_id`),
|
||||
KEY `teams_courseteam_team_id_594700d19b04f922_fk_teams_courseteam_id` (`team_id`),
|
||||
CONSTRAINT `teams_courseteam_team_id_594700d19b04f922_fk_teams_courseteam_id` FOREIGN KEY (`team_id`) REFERENCES `teams_courseteam` (`id`),
|
||||
CONSTRAINT `teams_courseteammembers_user_id_2d93b28be22c3c40_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `teams_courseteammembers_user_id_2d93b28be22c3c40_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `teams_courseteam_team_id_594700d19b04f922_fk_teams_courseteam_id` FOREIGN KEY (`team_id`) REFERENCES `teams_courseteam` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `third_party_auth_ltiproviderconfig`;
|
||||
@@ -3962,8 +3995,8 @@ CREATE TABLE `verify_student_skippedreverification` (
|
||||
KEY `verify_student_skippedreverification_ea134da7` (`course_id`),
|
||||
KEY `verify_student_skippedreverification_bef2d98a` (`checkpoint_id`),
|
||||
KEY `verify_student_skippedreverification_e8701ad4` (`user_id`),
|
||||
CONSTRAINT `D759ffa5ca66ef1a2c8c200f7a21365b` FOREIGN KEY (`checkpoint_id`) REFERENCES `verify_student_verificationcheckpoint` (`id`),
|
||||
CONSTRAINT `verify_student_skippedr_user_id_6752b392e3d3c501_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `verify_student_skippedr_user_id_6752b392e3d3c501_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `D759ffa5ca66ef1a2c8c200f7a21365b` FOREIGN KEY (`checkpoint_id`) REFERENCES `verify_student_verificationcheckpoint` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `verify_student_softwaresecurephotoverification`;
|
||||
@@ -3997,9 +4030,9 @@ CREATE TABLE `verify_student_softwaresecurephotoverification` (
|
||||
KEY `verify_student_softwaresecurephotoverification_afd1a1a8` (`updated_at`),
|
||||
KEY `verify_student_softwaresecurephotoverification_ebf78b51` (`display`),
|
||||
KEY `verify_student_softwaresecurephotoverification_22bb6ff9` (`submitted_at`),
|
||||
CONSTRAINT `verify_student_software_user_id_61ffab9c12020106_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `D01dce17b91c9382bd80d4be23a3e0cf` FOREIGN KEY (`copy_id_photo_from_id`) REFERENCES `verify_student_softwaresecurephotoverification` (`id`),
|
||||
CONSTRAINT `verify_studen_reviewing_user_id_727fae1d0bcf8aaf_fk_auth_user_id` FOREIGN KEY (`reviewing_user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `verify_student_software_user_id_61ffab9c12020106_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `verify_studen_reviewing_user_id_727fae1d0bcf8aaf_fk_auth_user_id` FOREIGN KEY (`reviewing_user_id`) REFERENCES `auth_user` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `verify_student_verificationcheckpoint`;
|
||||
@@ -4057,8 +4090,8 @@ CREATE TABLE `verify_student_verificationstatus` (
|
||||
KEY `D4cefb6d3d71c9b26af2a5ece4c37277` (`checkpoint_id`),
|
||||
KEY `verify_student_verifica_user_id_5c19fcd6dc05f211_fk_auth_user_id` (`user_id`),
|
||||
KEY `verify_student_verificationstatus_9acb4454` (`status`),
|
||||
CONSTRAINT `D4cefb6d3d71c9b26af2a5ece4c37277` FOREIGN KEY (`checkpoint_id`) REFERENCES `verify_student_verificationcheckpoint` (`id`),
|
||||
CONSTRAINT `verify_student_verifica_user_id_5c19fcd6dc05f211_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `verify_student_verifica_user_id_5c19fcd6dc05f211_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `D4cefb6d3d71c9b26af2a5ece4c37277` FOREIGN KEY (`checkpoint_id`) REFERENCES `verify_student_verificationcheckpoint` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `wiki_article`;
|
||||
@@ -4079,9 +4112,9 @@ CREATE TABLE `wiki_article` (
|
||||
UNIQUE KEY `current_revision_id` (`current_revision_id`),
|
||||
KEY `wiki_article_0e939a4f` (`group_id`),
|
||||
KEY `wiki_article_5e7b1936` (`owner_id`),
|
||||
CONSTRAINT `wiki_article_owner_id_b1c1e44609a378f_fk_auth_user_id` FOREIGN KEY (`owner_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `current_revision_id_42a9dbec1e0dd15c_fk_wiki_articlerevision_id` FOREIGN KEY (`current_revision_id`) REFERENCES `wiki_articlerevision` (`id`),
|
||||
CONSTRAINT `wiki_article_group_id_2b38601b6aa39f3d_fk_auth_group_id` FOREIGN KEY (`group_id`) REFERENCES `auth_group` (`id`),
|
||||
CONSTRAINT `wiki_article_owner_id_b1c1e44609a378f_fk_auth_user_id` FOREIGN KEY (`owner_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `wiki_article_group_id_2b38601b6aa39f3d_fk_auth_group_id` FOREIGN KEY (`group_id`) REFERENCES `auth_group` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `wiki_articleforobject`;
|
||||
@@ -4135,9 +4168,9 @@ CREATE TABLE `wiki_articlerevision` (
|
||||
UNIQUE KEY `wiki_articlerevision_article_id_4b4e7910c8e7b2d0_uniq` (`article_id`,`revision_number`),
|
||||
KEY `fae2b1c6e892c699844d5dda69aeb89e` (`previous_revision_id`),
|
||||
KEY `wiki_articlerevision_user_id_183520686b6ead55_fk_auth_user_id` (`user_id`),
|
||||
CONSTRAINT `wiki_articlerevision_user_id_183520686b6ead55_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `fae2b1c6e892c699844d5dda69aeb89e` FOREIGN KEY (`previous_revision_id`) REFERENCES `wiki_articlerevision` (`id`),
|
||||
CONSTRAINT `wiki_articlerevis_article_id_1f2c587981af1463_fk_wiki_article_id` FOREIGN KEY (`article_id`) REFERENCES `wiki_article` (`id`),
|
||||
CONSTRAINT `wiki_articlerevision_user_id_183520686b6ead55_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `wiki_articlerevis_article_id_1f2c587981af1463_fk_wiki_article_id` FOREIGN KEY (`article_id`) REFERENCES `wiki_article` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `wiki_attachment`;
|
||||
@@ -4175,9 +4208,9 @@ CREATE TABLE `wiki_attachmentrevision` (
|
||||
KEY `wiki_attachmentrevision_07ba63f5` (`attachment_id`),
|
||||
KEY `wiki_attachmentrevision_e8680b8a` (`previous_revision_id`),
|
||||
KEY `wiki_attachmentrevision_e8701ad4` (`user_id`),
|
||||
CONSTRAINT `wiki_attachmentrevision_user_id_427e3f452b4bfdcd_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `D68d5cd540b66f536228137e518081f8` FOREIGN KEY (`attachment_id`) REFERENCES `wiki_attachment` (`reusableplugin_ptr_id`),
|
||||
CONSTRAINT `D8c1f0a8f0ddceb9c3ebc94379fe22c9` FOREIGN KEY (`previous_revision_id`) REFERENCES `wiki_attachmentrevision` (`id`),
|
||||
CONSTRAINT `wiki_attachmentrevision_user_id_427e3f452b4bfdcd_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `D8c1f0a8f0ddceb9c3ebc94379fe22c9` FOREIGN KEY (`previous_revision_id`) REFERENCES `wiki_attachmentrevision` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `wiki_image`;
|
||||
@@ -4220,8 +4253,8 @@ CREATE TABLE `wiki_reusableplugin_articles` (
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `reusableplugin_id` (`reusableplugin_id`,`article_id`),
|
||||
KEY `wiki_reusableplug_article_id_5e893d3b3fb4f7fa_fk_wiki_article_id` (`article_id`),
|
||||
CONSTRAINT `a9f9f50fd4e8fdafe7ffc0c1a145fee3` FOREIGN KEY (`reusableplugin_id`) REFERENCES `wiki_reusableplugin` (`articleplugin_ptr_id`),
|
||||
CONSTRAINT `wiki_reusableplug_article_id_5e893d3b3fb4f7fa_fk_wiki_article_id` FOREIGN KEY (`article_id`) REFERENCES `wiki_article` (`id`)
|
||||
CONSTRAINT `wiki_reusableplug_article_id_5e893d3b3fb4f7fa_fk_wiki_article_id` FOREIGN KEY (`article_id`) REFERENCES `wiki_article` (`id`),
|
||||
CONSTRAINT `a9f9f50fd4e8fdafe7ffc0c1a145fee3` FOREIGN KEY (`reusableplugin_id`) REFERENCES `wiki_reusableplugin` (`articleplugin_ptr_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `wiki_revisionplugin`;
|
||||
@@ -4256,9 +4289,9 @@ CREATE TABLE `wiki_revisionpluginrevision` (
|
||||
KEY `wiki_revisionpluginrevision_b25eaab4` (`plugin_id`),
|
||||
KEY `wiki_revisionpluginrevision_e8680b8a` (`previous_revision_id`),
|
||||
KEY `wiki_revisionpluginrevision_e8701ad4` (`user_id`),
|
||||
CONSTRAINT `wiki_revisionpluginrevi_user_id_55a00bd0e2532762_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `D9574e2f57b828a85a24838761473871` FOREIGN KEY (`plugin_id`) REFERENCES `wiki_revisionplugin` (`articleplugin_ptr_id`),
|
||||
CONSTRAINT `e524c4f887e857f93c39356f7cf7d4df` FOREIGN KEY (`previous_revision_id`) REFERENCES `wiki_revisionpluginrevision` (`id`),
|
||||
CONSTRAINT `wiki_revisionpluginrevi_user_id_55a00bd0e2532762_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `e524c4f887e857f93c39356f7cf7d4df` FOREIGN KEY (`previous_revision_id`) REFERENCES `wiki_revisionpluginrevision` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `wiki_simpleplugin`;
|
||||
@@ -4295,9 +4328,9 @@ CREATE TABLE `wiki_urlpath` (
|
||||
KEY `wiki_urlpath_656442a0` (`tree_id`),
|
||||
KEY `wiki_urlpath_c9e9a848` (`level`),
|
||||
KEY `wiki_urlpath_6be37982` (`parent_id`),
|
||||
CONSTRAINT `wiki_urlpath_site_id_4f30e731b0464e80_fk_django_site_id` FOREIGN KEY (`site_id`) REFERENCES `django_site` (`id`),
|
||||
CONSTRAINT `wiki_urlpath_article_id_1d1c5eb9a64e1390_fk_wiki_article_id` FOREIGN KEY (`article_id`) REFERENCES `wiki_article` (`id`),
|
||||
CONSTRAINT `wiki_urlpath_parent_id_24eab80cd168595f_fk_wiki_urlpath_id` FOREIGN KEY (`parent_id`) REFERENCES `wiki_urlpath` (`id`),
|
||||
CONSTRAINT `wiki_urlpath_site_id_4f30e731b0464e80_fk_django_site_id` FOREIGN KEY (`site_id`) REFERENCES `django_site` (`id`)
|
||||
CONSTRAINT `wiki_urlpath_parent_id_24eab80cd168595f_fk_wiki_urlpath_id` FOREIGN KEY (`parent_id`) REFERENCES `wiki_urlpath` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `workflow_assessmentworkflow`;
|
||||
@@ -4374,6 +4407,7 @@ CREATE TABLE `xblock_django_xblockdisableconfig` (
|
||||
`enabled` tinyint(1) NOT NULL,
|
||||
`disabled_blocks` longtext NOT NULL,
|
||||
`changed_by_id` int(11) DEFAULT NULL,
|
||||
`disabled_create_blocks` longtext NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `xblock_django_xbl_changed_by_id_429bdccb9201831c_fk_auth_user_id` (`changed_by_id`),
|
||||
CONSTRAINT `xblock_django_xbl_changed_by_id_429bdccb9201831c_fk_auth_user_id` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`)
|
||||
@@ -0,0 +1,48 @@
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
/*!40101 SET NAMES utf8 */;
|
||||
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||
DROP TABLE IF EXISTS `coursewarehistoryextended_studentmodulehistoryextended`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `coursewarehistoryextended_studentmodulehistoryextended` (
|
||||
`version` varchar(255) DEFAULT NULL,
|
||||
`created` datetime(6) NOT NULL,
|
||||
`state` longtext,
|
||||
`grade` double DEFAULT NULL,
|
||||
`max_grade` double DEFAULT NULL,
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`student_module_id` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `coursewarehistoryextended_studentmodulehistoryextended_2af72f10` (`version`),
|
||||
KEY `coursewarehistoryextended_studentmodulehistoryextended_e2fa5388` (`created`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `django_migrations`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `django_migrations` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`app` varchar(255) NOT NULL,
|
||||
`name` varchar(255) NOT NULL,
|
||||
`applied` datetime(6) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=119 DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||
|
||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||
|
||||
Binary file not shown.
BIN
common/test/db_cache/lettuce_student_module_history.db
Normal file
BIN
common/test/db_cache/lettuce_student_module_history.db
Normal file
Binary file not shown.
@@ -46,6 +46,8 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
|
||||
providers.
|
||||
"""
|
||||
__test__ = False
|
||||
# Tell Django to clean out all databases, not just default
|
||||
multi_db = True
|
||||
|
||||
# TEST_DATA must be overridden by subclasses
|
||||
TEST_DATA = None
|
||||
@@ -151,7 +153,10 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
|
||||
"""
|
||||
return check_sum_of_calls(XBlock, ['__init__'], instantiations, instantiations, include_arguments=False)
|
||||
|
||||
def instrument_course_progress_render(self, course_width, enable_ccx, view_as_ccx, queries, reads, xblocks):
|
||||
def instrument_course_progress_render(
|
||||
self, course_width, enable_ccx, view_as_ccx,
|
||||
default_queries, history_queries, reads, xblocks
|
||||
):
|
||||
"""
|
||||
Renders the progress page, instrumenting Mongo reads and SQL queries.
|
||||
"""
|
||||
@@ -173,10 +178,11 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
|
||||
# can actually take affect.
|
||||
OverrideFieldData.provider_classes = None
|
||||
|
||||
with self.assertNumQueries(queries):
|
||||
with self.assertMongoCallCount(reads):
|
||||
with self.assertXBlockInstantiations(xblocks):
|
||||
self.grade_course(self.course, view_as_ccx)
|
||||
with self.assertNumQueries(default_queries, using='default'):
|
||||
with self.assertNumQueries(history_queries, using='student_module_history'):
|
||||
with self.assertMongoCallCount(reads):
|
||||
with self.assertXBlockInstantiations(xblocks):
|
||||
self.grade_course(self.course, view_as_ccx)
|
||||
|
||||
@ddt.data(*itertools.product(('no_overrides', 'ccx'), range(1, 4), (True, False), (True, False)))
|
||||
@ddt.unpack
|
||||
@@ -201,8 +207,12 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
|
||||
raise SkipTest("Can't use a MongoModulestore test as a CCX course")
|
||||
|
||||
with self.settings(FIELD_OVERRIDE_PROVIDERS=providers[overrides]):
|
||||
queries, reads, xblocks = self.TEST_DATA[(overrides, course_width, enable_ccx, view_as_ccx)]
|
||||
self.instrument_course_progress_render(course_width, enable_ccx, view_as_ccx, queries, reads, xblocks)
|
||||
default_queries, history_queries, reads, xblocks = self.TEST_DATA[
|
||||
(overrides, course_width, enable_ccx, view_as_ccx)
|
||||
]
|
||||
self.instrument_course_progress_render(
|
||||
course_width, enable_ccx, view_as_ccx, default_queries, history_queries, reads, xblocks
|
||||
)
|
||||
|
||||
|
||||
class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
|
||||
@@ -213,25 +223,30 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
|
||||
__test__ = True
|
||||
|
||||
TEST_DATA = {
|
||||
# (providers, course_width, enable_ccx, view_as_ccx): # of sql queries, # of mongo queries, # of xblocks
|
||||
('no_overrides', 1, True, False): (48, 6, 13),
|
||||
('no_overrides', 2, True, False): (135, 6, 84),
|
||||
('no_overrides', 3, True, False): (480, 6, 335),
|
||||
('ccx', 1, True, False): (48, 6, 13),
|
||||
('ccx', 2, True, False): (135, 6, 84),
|
||||
('ccx', 3, True, False): (480, 6, 335),
|
||||
('ccx', 1, True, True): (48, 6, 13),
|
||||
('ccx', 2, True, True): (135, 6, 84),
|
||||
('ccx', 3, True, True): (480, 6, 335),
|
||||
('no_overrides', 1, False, False): (48, 6, 13),
|
||||
('no_overrides', 2, False, False): (135, 6, 84),
|
||||
('no_overrides', 3, False, False): (480, 6, 335),
|
||||
('ccx', 1, False, False): (48, 6, 13),
|
||||
('ccx', 2, False, False): (135, 6, 84),
|
||||
('ccx', 3, False, False): (480, 6, 335),
|
||||
('ccx', 1, False, True): (48, 6, 13),
|
||||
('ccx', 2, False, True): (135, 6, 84),
|
||||
('ccx', 3, False, True): (480, 6, 335),
|
||||
# (providers, course_width, enable_ccx, view_as_ccx): (
|
||||
# # of sql queries to default,
|
||||
# # sql queries to student_module_history,
|
||||
# # of mongo queries,
|
||||
# # of xblocks
|
||||
# )
|
||||
('no_overrides', 1, True, False): (47, 1, 6, 13),
|
||||
('no_overrides', 2, True, False): (119, 16, 6, 84),
|
||||
('no_overrides', 3, True, False): (399, 81, 6, 335),
|
||||
('ccx', 1, True, False): (47, 1, 6, 13),
|
||||
('ccx', 2, True, False): (119, 16, 6, 84),
|
||||
('ccx', 3, True, False): (399, 81, 6, 335),
|
||||
('ccx', 1, True, True): (47, 1, 6, 13),
|
||||
('ccx', 2, True, True): (119, 16, 6, 84),
|
||||
('ccx', 3, True, True): (399, 81, 6, 335),
|
||||
('no_overrides', 1, False, False): (47, 1, 6, 13),
|
||||
('no_overrides', 2, False, False): (119, 16, 6, 84),
|
||||
('no_overrides', 3, False, False): (399, 81, 6, 335),
|
||||
('ccx', 1, False, False): (47, 1, 6, 13),
|
||||
('ccx', 2, False, False): (119, 16, 6, 84),
|
||||
('ccx', 3, False, False): (399, 81, 6, 335),
|
||||
('ccx', 1, False, True): (47, 1, 6, 13),
|
||||
('ccx', 2, False, True): (119, 16, 6, 84),
|
||||
('ccx', 3, False, True): (399, 81, 6, 335),
|
||||
}
|
||||
|
||||
|
||||
@@ -243,22 +258,22 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase):
|
||||
__test__ = True
|
||||
|
||||
TEST_DATA = {
|
||||
('no_overrides', 1, True, False): (48, 4, 9),
|
||||
('no_overrides', 2, True, False): (135, 19, 54),
|
||||
('no_overrides', 3, True, False): (480, 84, 215),
|
||||
('ccx', 1, True, False): (48, 4, 9),
|
||||
('ccx', 2, True, False): (135, 19, 54),
|
||||
('ccx', 3, True, False): (480, 84, 215),
|
||||
('ccx', 1, True, True): (50, 4, 13),
|
||||
('ccx', 2, True, True): (137, 19, 84),
|
||||
('ccx', 3, True, True): (482, 84, 335),
|
||||
('no_overrides', 1, False, False): (48, 4, 9),
|
||||
('no_overrides', 2, False, False): (135, 19, 54),
|
||||
('no_overrides', 3, False, False): (480, 84, 215),
|
||||
('ccx', 1, False, False): (48, 4, 9),
|
||||
('ccx', 2, False, False): (135, 19, 54),
|
||||
('ccx', 3, False, False): (480, 84, 215),
|
||||
('ccx', 1, False, True): (48, 4, 9),
|
||||
('ccx', 2, False, True): (135, 19, 54),
|
||||
('ccx', 3, False, True): (480, 84, 215),
|
||||
('no_overrides', 1, True, False): (47, 1, 4, 9),
|
||||
('no_overrides', 2, True, False): (119, 16, 19, 54),
|
||||
('no_overrides', 3, True, False): (399, 81, 84, 215),
|
||||
('ccx', 1, True, False): (47, 1, 4, 9),
|
||||
('ccx', 2, True, False): (119, 16, 19, 54),
|
||||
('ccx', 3, True, False): (399, 81, 84, 215),
|
||||
('ccx', 1, True, True): (49, 1, 4, 13),
|
||||
('ccx', 2, True, True): (121, 16, 19, 84),
|
||||
('ccx', 3, True, True): (401, 81, 84, 335),
|
||||
('no_overrides', 1, False, False): (47, 1, 4, 9),
|
||||
('no_overrides', 2, False, False): (119, 16, 19, 54),
|
||||
('no_overrides', 3, False, False): (399, 81, 84, 215),
|
||||
('ccx', 1, False, False): (47, 1, 4, 9),
|
||||
('ccx', 2, False, False): (119, 16, 19, 54),
|
||||
('ccx', 3, False, False): (399, 81, 84, 215),
|
||||
('ccx', 1, False, True): (47, 1, 4, 9),
|
||||
('ccx', 2, False, True): (119, 16, 19, 54),
|
||||
('ccx', 3, False, True): (399, 81, 84, 215),
|
||||
}
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
"""A command to clean the StudentModuleHistory table.
|
||||
|
||||
When we added XBlock storage, each field modification wrote a new history row
|
||||
to the db. Now that we have bulk saves to avoid that database hammering, we
|
||||
need to clean out the unnecessary rows from the database.
|
||||
|
||||
This command that does that.
|
||||
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import optparse
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from django.core.management.base import NoArgsCommand
|
||||
from django.db import transaction
|
||||
from django.db.models import Max
|
||||
from courseware.models import StudentModuleHistory
|
||||
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
"""The actual clean_history command to clean history rows."""
|
||||
|
||||
help = "Deletes unneeded rows from the StudentModuleHistory table."
|
||||
|
||||
option_list = NoArgsCommand.option_list + (
|
||||
optparse.make_option(
|
||||
'--batch',
|
||||
type='int',
|
||||
default=100,
|
||||
help="Batch size, number of module_ids to examine in a transaction.",
|
||||
),
|
||||
optparse.make_option(
|
||||
'--dry-run',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="Don't change the database, just show what would be done.",
|
||||
),
|
||||
optparse.make_option(
|
||||
'--sleep',
|
||||
type='float',
|
||||
default=0,
|
||||
help="Seconds to sleep between batches.",
|
||||
),
|
||||
)
|
||||
|
||||
def handle_noargs(self, **options):
|
||||
# We don't want to see the SQL output from the db layer.
|
||||
logging.getLogger("django.db.backends").setLevel(logging.INFO)
|
||||
|
||||
smhc = StudentModuleHistoryCleaner(
|
||||
dry_run=options["dry_run"],
|
||||
)
|
||||
smhc.main(batch_size=options["batch"], sleep=options["sleep"])
|
||||
|
||||
|
||||
class StudentModuleHistoryCleaner(object):
|
||||
"""Logic to clean rows from the StudentModuleHistory table."""
|
||||
|
||||
DELETE_GAP_SECS = 0.5 # Rows this close can be discarded.
|
||||
STATE_FILE = "clean_history.json"
|
||||
BATCH_SIZE = 100
|
||||
|
||||
def __init__(self, dry_run=False):
|
||||
self.dry_run = dry_run
|
||||
self.next_student_module_id = 0
|
||||
self.last_student_module_id = 0
|
||||
|
||||
def main(self, batch_size=None, sleep=0):
|
||||
"""Invoked from the management command to do all the work."""
|
||||
|
||||
batch_size = batch_size or self.BATCH_SIZE
|
||||
|
||||
self.last_student_module_id = self.get_last_student_module_id()
|
||||
self.load_state()
|
||||
|
||||
while self.next_student_module_id <= self.last_student_module_id:
|
||||
with transaction.atomic():
|
||||
for smid in self.module_ids_to_check(batch_size):
|
||||
try:
|
||||
self.clean_one_student_module(smid)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
trace = traceback.format_exc()
|
||||
self.say("Couldn't clean student_module_id {}:\n{}".format(smid, trace))
|
||||
if self.dry_run:
|
||||
transaction.set_rollback(True)
|
||||
else:
|
||||
self.say("Committing")
|
||||
self.save_state()
|
||||
if sleep:
|
||||
time.sleep(sleep)
|
||||
|
||||
def say(self, message):
|
||||
"""
|
||||
Display a message to the user.
|
||||
|
||||
The message will have a trailing newline added to it.
|
||||
|
||||
"""
|
||||
print message
|
||||
|
||||
def load_state(self):
|
||||
"""
|
||||
Load the latest state from disk.
|
||||
"""
|
||||
try:
|
||||
state_file = open(self.STATE_FILE)
|
||||
except IOError:
|
||||
self.say("No stored state")
|
||||
self.next_student_module_id = 0
|
||||
else:
|
||||
with state_file:
|
||||
state = json.load(state_file)
|
||||
self.say(
|
||||
"Loaded stored state: {}".format(
|
||||
json.dumps(state, sort_keys=True)
|
||||
)
|
||||
)
|
||||
self.next_student_module_id = state['next_student_module_id']
|
||||
|
||||
def save_state(self):
|
||||
"""
|
||||
Save the state to disk.
|
||||
"""
|
||||
state = {
|
||||
'next_student_module_id': self.next_student_module_id,
|
||||
}
|
||||
with open(self.STATE_FILE, "w") as state_file:
|
||||
json.dump(state, state_file)
|
||||
self.say("Saved state: {}".format(json.dumps(state, sort_keys=True)))
|
||||
|
||||
def get_last_student_module_id(self):
|
||||
"""
|
||||
Return the id of the last student_module.
|
||||
"""
|
||||
last = StudentModuleHistory.objects.all() \
|
||||
.aggregate(Max('student_module'))['student_module__max']
|
||||
self.say("Last student_module_id is {}".format(last))
|
||||
return last
|
||||
|
||||
def module_ids_to_check(self, batch_size):
|
||||
"""Produce a sequence of student module ids to check.
|
||||
|
||||
`batch_size` is how many module ids to produce, max.
|
||||
|
||||
The sequence starts with `next_student_module_id`, and goes up to
|
||||
and including `last_student_module_id`.
|
||||
|
||||
`next_student_module_id` is updated as each id is yielded.
|
||||
|
||||
"""
|
||||
start = self.next_student_module_id
|
||||
for smid in range(start, start + batch_size):
|
||||
if smid > self.last_student_module_id:
|
||||
break
|
||||
yield smid
|
||||
self.next_student_module_id = smid + 1
|
||||
|
||||
def get_history_for_student_modules(self, student_module_id):
|
||||
"""
|
||||
Get the history rows for a student module.
|
||||
|
||||
```student_module_id```: the id of the student module we're
|
||||
interested in.
|
||||
|
||||
Return a list: [(id, created), ...], all the rows of history.
|
||||
|
||||
"""
|
||||
history = StudentModuleHistory.objects \
|
||||
.filter(student_module=student_module_id) \
|
||||
.order_by('created', 'id')
|
||||
|
||||
return [(row.id, row.created) for row in history]
|
||||
|
||||
def delete_history(self, ids_to_delete):
|
||||
"""
|
||||
Delete history rows.
|
||||
|
||||
```ids_to_delete```: a non-empty list (or set...) of history row ids to delete.
|
||||
|
||||
"""
|
||||
assert ids_to_delete
|
||||
StudentModuleHistory.objects.filter(id__in=ids_to_delete).delete()
|
||||
|
||||
def clean_one_student_module(self, student_module_id):
|
||||
"""Clean one StudentModule's-worth of history.
|
||||
|
||||
`student_module_id`: the id of the StudentModule to process.
|
||||
|
||||
"""
|
||||
delete_gap = datetime.timedelta(seconds=self.DELETE_GAP_SECS)
|
||||
|
||||
history = self.get_history_for_student_modules(student_module_id)
|
||||
if not history:
|
||||
self.say("No history for student_module_id {}".format(student_module_id))
|
||||
return
|
||||
|
||||
ids_to_delete = []
|
||||
next_created = None
|
||||
for history_id, created in reversed(history):
|
||||
if next_created is not None:
|
||||
# Compare this timestamp with the next one.
|
||||
if (next_created - created) < delete_gap:
|
||||
# This row is followed closely by another, we can discard
|
||||
# this one.
|
||||
ids_to_delete.append(history_id)
|
||||
|
||||
next_created = created
|
||||
|
||||
verb = "Would have deleted" if self.dry_run else "Deleting"
|
||||
self.say("{verb} {to_delete} rows of {total} for student_module_id {id}".format(
|
||||
verb=verb,
|
||||
to_delete=len(ids_to_delete),
|
||||
total=len(history),
|
||||
id=student_module_id,
|
||||
))
|
||||
|
||||
if ids_to_delete and not self.dry_run:
|
||||
self.delete_history(ids_to_delete)
|
||||
@@ -1,491 +0,0 @@
|
||||
"""Test the clean_history management command."""
|
||||
|
||||
import fnmatch
|
||||
from mock import Mock
|
||||
from nose.plugins.attrib import attr
|
||||
import os.path
|
||||
import textwrap
|
||||
|
||||
import dateutil.parser
|
||||
|
||||
from django.test import TransactionTestCase
|
||||
from django.db import connection
|
||||
|
||||
from courseware.management.commands.clean_history import StudentModuleHistoryCleaner
|
||||
|
||||
# In lots of places in this file, smhc == StudentModuleHistoryCleaner
|
||||
|
||||
|
||||
def parse_date(sdate):
|
||||
"""Parse a string date into a datetime."""
|
||||
parsed = dateutil.parser.parse(sdate)
|
||||
parsed = parsed.replace(tzinfo=dateutil.tz.gettz('UTC'))
|
||||
return parsed
|
||||
|
||||
|
||||
class SmhcSayStubbed(StudentModuleHistoryCleaner):
|
||||
"""StudentModuleHistoryCleaner, but with .say() stubbed for testing."""
|
||||
def __init__(self, **kwargs):
|
||||
super(SmhcSayStubbed, self).__init__(**kwargs)
|
||||
self.said_lines = []
|
||||
|
||||
def say(self, msg):
|
||||
self.said_lines.append(msg)
|
||||
|
||||
|
||||
class SmhcDbMocked(SmhcSayStubbed):
|
||||
"""StudentModuleHistoryCleaner, but with db access mocked."""
|
||||
def __init__(self, **kwargs):
|
||||
super(SmhcDbMocked, self).__init__(**kwargs)
|
||||
self.get_history_for_student_modules = Mock()
|
||||
self.delete_history = Mock()
|
||||
|
||||
def set_rows(self, rows):
|
||||
"""Set the mocked history rows."""
|
||||
rows = [(row_id, parse_date(created)) for row_id, created in rows]
|
||||
self.get_history_for_student_modules.return_value = rows
|
||||
|
||||
|
||||
class HistoryCleanerTest(TransactionTestCase):
|
||||
"""Base class for all history cleaner tests."""
|
||||
|
||||
maxDiff = None
|
||||
|
||||
def setUp(self):
|
||||
super(HistoryCleanerTest, self).setUp()
|
||||
self.addCleanup(self.clean_up_state_file)
|
||||
|
||||
def write_state_file(self, state):
|
||||
"""Write the string `state` into the state file read by StudentModuleHistoryCleaner."""
|
||||
with open(StudentModuleHistoryCleaner.STATE_FILE, "w") as state_file:
|
||||
state_file.write(state)
|
||||
|
||||
def read_state_file(self):
|
||||
"""Return the string contents of the state file read by StudentModuleHistoryCleaner."""
|
||||
with open(StudentModuleHistoryCleaner.STATE_FILE) as state_file:
|
||||
return state_file.read()
|
||||
|
||||
def clean_up_state_file(self):
|
||||
"""Remove any state file lying around."""
|
||||
if os.path.exists(StudentModuleHistoryCleaner.STATE_FILE):
|
||||
os.remove(StudentModuleHistoryCleaner.STATE_FILE)
|
||||
|
||||
def assert_said(self, smhc, *msgs):
|
||||
"""Fail if the `smhc` didn't say `msgs`.
|
||||
|
||||
The messages passed here are `fnmatch`-style patterns: "*" means anything.
|
||||
|
||||
"""
|
||||
for said, pattern in zip(smhc.said_lines, msgs):
|
||||
if not fnmatch.fnmatch(said, pattern):
|
||||
fmt = textwrap.dedent("""\
|
||||
Messages:
|
||||
|
||||
{msgs}
|
||||
|
||||
don't match patterns:
|
||||
|
||||
{patterns}
|
||||
|
||||
Failed at {said!r} and {pattern!r}
|
||||
""")
|
||||
|
||||
msg = fmt.format(
|
||||
msgs="\n".join(smhc.said_lines),
|
||||
patterns="\n".join(msgs),
|
||||
said=said,
|
||||
pattern=pattern
|
||||
)
|
||||
self.fail(msg)
|
||||
|
||||
def parse_rows(self, rows):
|
||||
"""Parse convenient rows into real data."""
|
||||
rows = [
|
||||
(row_id, parse_date(created), student_module_id)
|
||||
for row_id, created, student_module_id in rows
|
||||
]
|
||||
return rows
|
||||
|
||||
def write_history(self, rows):
|
||||
"""Write history rows to the db.
|
||||
|
||||
Each row should be (id, created, student_module_id).
|
||||
|
||||
"""
|
||||
cursor = connection.cursor()
|
||||
cursor.executemany(
|
||||
"""
|
||||
INSERT INTO courseware_studentmodulehistory
|
||||
(id, created, student_module_id)
|
||||
VALUES (%s, %s, %s)
|
||||
""",
|
||||
self.parse_rows(rows),
|
||||
)
|
||||
|
||||
def read_history(self):
|
||||
"""Read the history from the db, and return it as a list of tuples.
|
||||
|
||||
Returns [(id, created, student_module_id), ...]
|
||||
|
||||
"""
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("""
|
||||
SELECT id, created, student_module_id FROM courseware_studentmodulehistory
|
||||
""")
|
||||
return cursor.fetchall()
|
||||
|
||||
def assert_history(self, rows):
|
||||
"""Assert that the history rows are the same as `rows`."""
|
||||
self.assertEqual(self.parse_rows(rows), self.read_history())
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class HistoryCleanerNoDbTest(HistoryCleanerTest):
|
||||
"""Tests of StudentModuleHistoryCleaner with db access mocked."""
|
||||
|
||||
def test_empty(self):
|
||||
smhc = SmhcDbMocked()
|
||||
smhc.set_rows([])
|
||||
|
||||
smhc.clean_one_student_module(1)
|
||||
self.assert_said(smhc, "No history for student_module_id 1")
|
||||
|
||||
# Nothing to delete, so delete_history wasn't called.
|
||||
self.assertFalse(smhc.delete_history.called)
|
||||
|
||||
def test_one_row(self):
|
||||
smhc = SmhcDbMocked()
|
||||
smhc.set_rows([
|
||||
(1, "2013-07-13 12:11:10.987"),
|
||||
])
|
||||
smhc.clean_one_student_module(1)
|
||||
self.assert_said(smhc, "Deleting 0 rows of 1 for student_module_id 1")
|
||||
# Nothing to delete, so delete_history wasn't called.
|
||||
self.assertFalse(smhc.delete_history.called)
|
||||
|
||||
def test_one_row_dry_run(self):
|
||||
smhc = SmhcDbMocked(dry_run=True)
|
||||
smhc.set_rows([
|
||||
(1, "2013-07-13 12:11:10.987"),
|
||||
])
|
||||
smhc.clean_one_student_module(1)
|
||||
self.assert_said(smhc, "Would have deleted 0 rows of 1 for student_module_id 1")
|
||||
# Nothing to delete, so delete_history wasn't called.
|
||||
self.assertFalse(smhc.delete_history.called)
|
||||
|
||||
def test_two_rows_close(self):
|
||||
smhc = SmhcDbMocked()
|
||||
smhc.set_rows([
|
||||
(7, "2013-07-13 12:34:56.789"),
|
||||
(9, "2013-07-13 12:34:56.987"),
|
||||
])
|
||||
smhc.clean_one_student_module(1)
|
||||
self.assert_said(smhc, "Deleting 1 rows of 2 for student_module_id 1")
|
||||
smhc.delete_history.assert_called_once_with([7])
|
||||
|
||||
def test_two_rows_far(self):
|
||||
smhc = SmhcDbMocked()
|
||||
smhc.set_rows([
|
||||
(7, "2013-07-13 12:34:56.789"),
|
||||
(9, "2013-07-13 12:34:57.890"),
|
||||
])
|
||||
smhc.clean_one_student_module(1)
|
||||
self.assert_said(smhc, "Deleting 0 rows of 2 for student_module_id 1")
|
||||
self.assertFalse(smhc.delete_history.called)
|
||||
|
||||
def test_a_bunch_of_rows(self):
|
||||
smhc = SmhcDbMocked()
|
||||
smhc.set_rows([
|
||||
(4, "2013-07-13 16:30:00.000"), # keep
|
||||
(8, "2013-07-13 16:30:01.100"),
|
||||
(15, "2013-07-13 16:30:01.200"),
|
||||
(16, "2013-07-13 16:30:01.300"), # keep
|
||||
(23, "2013-07-13 16:30:02.400"),
|
||||
(42, "2013-07-13 16:30:02.500"),
|
||||
(98, "2013-07-13 16:30:02.600"), # keep
|
||||
(99, "2013-07-13 16:30:59.000"), # keep
|
||||
])
|
||||
smhc.clean_one_student_module(17)
|
||||
self.assert_said(smhc, "Deleting 4 rows of 8 for student_module_id 17")
|
||||
smhc.delete_history.assert_called_once_with([42, 23, 15, 8])
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class HistoryCleanerWitDbTest(HistoryCleanerTest):
|
||||
"""Tests of StudentModuleHistoryCleaner with a real db."""
|
||||
|
||||
def test_no_history(self):
|
||||
# Cleaning a student_module_id with no history leaves the db unchanged.
|
||||
smhc = SmhcSayStubbed()
|
||||
self.write_history([
|
||||
(4, "2013-07-13 16:30:00.000", 11), # keep
|
||||
(8, "2013-07-13 16:30:01.100", 11),
|
||||
(15, "2013-07-13 16:30:01.200", 11),
|
||||
(16, "2013-07-13 16:30:01.300", 11), # keep
|
||||
(23, "2013-07-13 16:30:02.400", 11),
|
||||
(42, "2013-07-13 16:30:02.500", 11),
|
||||
(98, "2013-07-13 16:30:02.600", 11), # keep
|
||||
(99, "2013-07-13 16:30:59.000", 11), # keep
|
||||
])
|
||||
|
||||
smhc.clean_one_student_module(22)
|
||||
self.assert_said(smhc, "No history for student_module_id 22")
|
||||
self.assert_history([
|
||||
(4, "2013-07-13 16:30:00.000", 11), # keep
|
||||
(8, "2013-07-13 16:30:01.100", 11),
|
||||
(15, "2013-07-13 16:30:01.200", 11),
|
||||
(16, "2013-07-13 16:30:01.300", 11), # keep
|
||||
(23, "2013-07-13 16:30:02.400", 11),
|
||||
(42, "2013-07-13 16:30:02.500", 11),
|
||||
(98, "2013-07-13 16:30:02.600", 11), # keep
|
||||
(99, "2013-07-13 16:30:59.000", 11), # keep
|
||||
])
|
||||
|
||||
def test_a_bunch_of_rows(self):
|
||||
# Cleaning a student_module_id with 8 records, 4 to delete.
|
||||
smhc = SmhcSayStubbed()
|
||||
self.write_history([
|
||||
(4, "2013-07-13 16:30:00.000", 11), # keep
|
||||
(8, "2013-07-13 16:30:01.100", 11),
|
||||
(15, "2013-07-13 16:30:01.200", 11),
|
||||
(16, "2013-07-13 16:30:01.300", 11), # keep
|
||||
(17, "2013-07-13 16:30:01.310", 22), # other student_module_id!
|
||||
(23, "2013-07-13 16:30:02.400", 11),
|
||||
(42, "2013-07-13 16:30:02.500", 11),
|
||||
(98, "2013-07-13 16:30:02.600", 11), # keep
|
||||
(99, "2013-07-13 16:30:59.000", 11), # keep
|
||||
])
|
||||
|
||||
smhc.clean_one_student_module(11)
|
||||
self.assert_said(smhc, "Deleting 4 rows of 8 for student_module_id 11")
|
||||
self.assert_history([
|
||||
(4, "2013-07-13 16:30:00.000", 11), # keep
|
||||
(16, "2013-07-13 16:30:01.300", 11), # keep
|
||||
(17, "2013-07-13 16:30:01.310", 22), # other student_module_id!
|
||||
(98, "2013-07-13 16:30:02.600", 11), # keep
|
||||
(99, "2013-07-13 16:30:59.000", 11), # keep
|
||||
])
|
||||
|
||||
def test_a_bunch_of_rows_dry_run(self):
|
||||
# Cleaning a student_module_id with 8 records, 4 to delete,
|
||||
# but don't really do it.
|
||||
smhc = SmhcSayStubbed(dry_run=True)
|
||||
self.write_history([
|
||||
(4, "2013-07-13 16:30:00.000", 11), # keep
|
||||
(8, "2013-07-13 16:30:01.100", 11),
|
||||
(15, "2013-07-13 16:30:01.200", 11),
|
||||
(16, "2013-07-13 16:30:01.300", 11), # keep
|
||||
(23, "2013-07-13 16:30:02.400", 11),
|
||||
(42, "2013-07-13 16:30:02.500", 11),
|
||||
(98, "2013-07-13 16:30:02.600", 11), # keep
|
||||
(99, "2013-07-13 16:30:59.000", 11), # keep
|
||||
])
|
||||
|
||||
smhc.clean_one_student_module(11)
|
||||
self.assert_said(smhc, "Would have deleted 4 rows of 8 for student_module_id 11")
|
||||
self.assert_history([
|
||||
(4, "2013-07-13 16:30:00.000", 11), # keep
|
||||
(8, "2013-07-13 16:30:01.100", 11),
|
||||
(15, "2013-07-13 16:30:01.200", 11),
|
||||
(16, "2013-07-13 16:30:01.300", 11), # keep
|
||||
(23, "2013-07-13 16:30:02.400", 11),
|
||||
(42, "2013-07-13 16:30:02.500", 11),
|
||||
(98, "2013-07-13 16:30:02.600", 11), # keep
|
||||
(99, "2013-07-13 16:30:59.000", 11), # keep
|
||||
])
|
||||
|
||||
def test_a_bunch_of_rows_in_jumbled_order(self):
|
||||
# Cleaning a student_module_id with 8 records, 4 to delete.
|
||||
smhc = SmhcSayStubbed()
|
||||
self.write_history([
|
||||
(23, "2013-07-13 16:30:01.100", 11),
|
||||
(24, "2013-07-13 16:30:01.300", 11), # keep
|
||||
(27, "2013-07-13 16:30:02.500", 11),
|
||||
(30, "2013-07-13 16:30:01.350", 22), # other student_module_id!
|
||||
(32, "2013-07-13 16:30:59.000", 11), # keep
|
||||
(50, "2013-07-13 16:30:02.400", 11),
|
||||
(51, "2013-07-13 16:30:02.600", 11), # keep
|
||||
(56, "2013-07-13 16:30:00.000", 11), # keep
|
||||
(57, "2013-07-13 16:30:01.200", 11),
|
||||
])
|
||||
|
||||
smhc.clean_one_student_module(11)
|
||||
self.assert_said(smhc, "Deleting 4 rows of 8 for student_module_id 11")
|
||||
self.assert_history([
|
||||
(24, "2013-07-13 16:30:01.300", 11), # keep
|
||||
(30, "2013-07-13 16:30:01.350", 22), # other student_module_id!
|
||||
(32, "2013-07-13 16:30:59.000", 11), # keep
|
||||
(51, "2013-07-13 16:30:02.600", 11), # keep
|
||||
(56, "2013-07-13 16:30:00.000", 11), # keep
|
||||
])
|
||||
|
||||
def test_a_bunch_of_rows_with_timestamp_ties(self):
|
||||
# Sometimes rows are written with identical timestamps. The one with
|
||||
# the greater id is the winner in that case.
|
||||
smhc = SmhcSayStubbed()
|
||||
self.write_history([
|
||||
(21, "2013-07-13 16:30:01.100", 11),
|
||||
(24, "2013-07-13 16:30:01.100", 11), # keep
|
||||
(22, "2013-07-13 16:30:01.100", 11),
|
||||
(23, "2013-07-13 16:30:01.100", 11),
|
||||
(27, "2013-07-13 16:30:02.500", 11),
|
||||
(30, "2013-07-13 16:30:01.350", 22), # other student_module_id!
|
||||
(32, "2013-07-13 16:30:59.000", 11), # keep
|
||||
(50, "2013-07-13 16:30:02.500", 11), # keep
|
||||
])
|
||||
|
||||
smhc.clean_one_student_module(11)
|
||||
self.assert_said(smhc, "Deleting 4 rows of 7 for student_module_id 11")
|
||||
self.assert_history([
|
||||
(24, "2013-07-13 16:30:01.100", 11), # keep
|
||||
(30, "2013-07-13 16:30:01.350", 22), # other student_module_id!
|
||||
(32, "2013-07-13 16:30:59.000", 11), # keep
|
||||
(50, "2013-07-13 16:30:02.500", 11), # keep
|
||||
])
|
||||
|
||||
def test_get_last_student_module(self):
|
||||
# Can we find the last student_module_id properly?
|
||||
smhc = SmhcSayStubbed()
|
||||
self.write_history([
|
||||
(23, "2013-07-13 16:30:01.100", 11),
|
||||
(24, "2013-07-13 16:30:01.300", 44),
|
||||
(27, "2013-07-13 16:30:02.500", 11),
|
||||
(30, "2013-07-13 16:30:01.350", 22),
|
||||
(32, "2013-07-13 16:30:59.000", 11),
|
||||
(51, "2013-07-13 16:30:02.600", 33),
|
||||
(56, "2013-07-13 16:30:00.000", 11),
|
||||
])
|
||||
last = smhc.get_last_student_module_id()
|
||||
self.assertEqual(last, 44)
|
||||
self.assert_said(smhc, "Last student_module_id is 44")
|
||||
|
||||
def test_load_state_with_no_stored_state(self):
|
||||
smhc = SmhcSayStubbed()
|
||||
self.assertFalse(os.path.exists(smhc.STATE_FILE))
|
||||
smhc.load_state()
|
||||
self.assertEqual(smhc.next_student_module_id, 0)
|
||||
self.assert_said(smhc, "No stored state")
|
||||
|
||||
def test_load_stored_state(self):
|
||||
self.write_state_file('{"next_student_module_id": 23}')
|
||||
smhc = SmhcSayStubbed()
|
||||
smhc.load_state()
|
||||
self.assertEqual(smhc.next_student_module_id, 23)
|
||||
self.assert_said(smhc, 'Loaded stored state: {"next_student_module_id": 23}')
|
||||
|
||||
def test_save_state(self):
|
||||
smhc = SmhcSayStubbed()
|
||||
smhc.next_student_module_id = 47
|
||||
smhc.save_state()
|
||||
state = self.read_state_file()
|
||||
self.assertEqual(state, '{"next_student_module_id": 47}')
|
||||
|
||||
|
||||
class SmhcForTestingMain(SmhcSayStubbed):
|
||||
"""A StudentModuleHistoryCleaner with a few function stubbed for testing main."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.exception_smids = kwargs.pop('exception_smids', ())
|
||||
super(SmhcForTestingMain, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean_one_student_module(self, smid):
|
||||
self.say("(not really cleaning {})".format(smid))
|
||||
if smid in self.exception_smids:
|
||||
raise Exception("Something went wrong!")
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class HistoryCleanerMainTest(HistoryCleanerTest):
|
||||
"""Tests of StudentModuleHistoryCleaner.main(), using SmhcForTestingMain."""
|
||||
|
||||
def test_only_one_record(self):
|
||||
smhc = SmhcForTestingMain()
|
||||
self.write_history([
|
||||
(1, "2013-07-15 11:47:00.000", 1),
|
||||
])
|
||||
smhc.main()
|
||||
self.assert_said(
|
||||
smhc,
|
||||
'Last student_module_id is 1',
|
||||
'No stored state',
|
||||
'(not really cleaning 0)',
|
||||
'(not really cleaning 1)',
|
||||
'Committing',
|
||||
'Saved state: {"next_student_module_id": 2}',
|
||||
)
|
||||
|
||||
def test_already_processed_some(self):
|
||||
smhc = SmhcForTestingMain()
|
||||
self.write_state_file('{"next_student_module_id": 25}')
|
||||
self.write_history([
|
||||
(1, "2013-07-15 15:04:00.000", 23),
|
||||
(2, "2013-07-15 15:04:11.000", 23),
|
||||
(3, "2013-07-15 15:04:01.000", 24),
|
||||
(4, "2013-07-15 15:04:00.000", 25),
|
||||
(5, "2013-07-15 15:04:00.000", 26),
|
||||
])
|
||||
smhc.main()
|
||||
self.assert_said(
|
||||
smhc,
|
||||
'Last student_module_id is 26',
|
||||
'Loaded stored state: {"next_student_module_id": 25}',
|
||||
'(not really cleaning 25)',
|
||||
'(not really cleaning 26)',
|
||||
'Committing',
|
||||
'Saved state: {"next_student_module_id": 27}'
|
||||
)
|
||||
|
||||
def test_working_in_batches(self):
|
||||
smhc = SmhcForTestingMain()
|
||||
self.write_state_file('{"next_student_module_id": 25}')
|
||||
self.write_history([
|
||||
(3, "2013-07-15 15:04:01.000", 24),
|
||||
(4, "2013-07-15 15:04:00.000", 25),
|
||||
(5, "2013-07-15 15:04:00.000", 26),
|
||||
(6, "2013-07-15 15:04:00.000", 27),
|
||||
(7, "2013-07-15 15:04:00.000", 28),
|
||||
(8, "2013-07-15 15:04:00.000", 29),
|
||||
])
|
||||
smhc.main(batch_size=3)
|
||||
self.assert_said(
|
||||
smhc,
|
||||
'Last student_module_id is 29',
|
||||
'Loaded stored state: {"next_student_module_id": 25}',
|
||||
'(not really cleaning 25)',
|
||||
'(not really cleaning 26)',
|
||||
'(not really cleaning 27)',
|
||||
'Committing',
|
||||
'Saved state: {"next_student_module_id": 28}',
|
||||
'(not really cleaning 28)',
|
||||
'(not really cleaning 29)',
|
||||
'Committing',
|
||||
'Saved state: {"next_student_module_id": 30}',
|
||||
)
|
||||
|
||||
def test_something_failing_while_cleaning(self):
|
||||
smhc = SmhcForTestingMain(exception_smids=[26])
|
||||
self.write_state_file('{"next_student_module_id": 25}')
|
||||
self.write_history([
|
||||
(3, "2013-07-15 15:04:01.000", 24),
|
||||
(4, "2013-07-15 15:04:00.000", 25),
|
||||
(5, "2013-07-15 15:04:00.000", 26),
|
||||
(6, "2013-07-15 15:04:00.000", 27),
|
||||
(7, "2013-07-15 15:04:00.000", 28),
|
||||
(8, "2013-07-15 15:04:00.000", 29),
|
||||
])
|
||||
smhc.main(batch_size=3)
|
||||
self.assert_said(
|
||||
smhc,
|
||||
'Last student_module_id is 29',
|
||||
'Loaded stored state: {"next_student_module_id": 25}',
|
||||
'(not really cleaning 25)',
|
||||
'(not really cleaning 26)',
|
||||
"Couldn't clean student_module_id 26:\nTraceback*Exception: Something went wrong!\n",
|
||||
'(not really cleaning 27)',
|
||||
'Committing',
|
||||
'Saved state: {"next_student_module_id": 28}',
|
||||
'(not really cleaning 28)',
|
||||
'(not really cleaning 29)',
|
||||
'Committing',
|
||||
'Saved state: {"next_student_module_id": 30}',
|
||||
)
|
||||
@@ -24,9 +24,9 @@ from django.dispatch import receiver, Signal
|
||||
from model_utils.models import TimeStampedModel
|
||||
from student.models import user_by_anonymous_id
|
||||
from submissions.models import score_set, score_reset
|
||||
import coursewarehistoryextended
|
||||
|
||||
from xmodule_django.models import CourseKeyField, LocationKeyField, BlockTypeKeyField
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
log = logging.getLogger("edx.courseware")
|
||||
|
||||
@@ -149,18 +149,15 @@ class StudentModule(models.Model):
|
||||
return unicode(repr(self))
|
||||
|
||||
|
||||
class StudentModuleHistory(models.Model):
|
||||
"""Keeps a complete history of state changes for a given XModule for a given
|
||||
Student. Right now, we restrict this to problems so that the table doesn't
|
||||
explode in size."""
|
||||
class BaseStudentModuleHistory(models.Model):
|
||||
"""Abstract class containing most fields used by any class
|
||||
storing Student Module History"""
|
||||
objects = ChunkingManager()
|
||||
HISTORY_SAVING_TYPES = {'problem'}
|
||||
|
||||
class Meta(object):
|
||||
app_label = "courseware"
|
||||
get_latest_by = "created"
|
||||
abstract = True
|
||||
|
||||
student_module = models.ForeignKey(StudentModule, db_index=True)
|
||||
version = models.CharField(max_length=255, null=True, blank=True, db_index=True)
|
||||
|
||||
# This should be populated from the modified field in StudentModule
|
||||
@@ -169,11 +166,59 @@ class StudentModuleHistory(models.Model):
|
||||
grade = models.FloatField(null=True, blank=True)
|
||||
max_grade = models.FloatField(null=True, blank=True)
|
||||
|
||||
@receiver(post_save, sender=StudentModule)
|
||||
@property
|
||||
def csm(self):
|
||||
"""
|
||||
Finds the StudentModule object for this history record, even if our data is split
|
||||
across multiple data stores. Django does not handle this correctly with the built-in
|
||||
student_module property.
|
||||
"""
|
||||
return StudentModule.objects.get(pk=self.student_module_id)
|
||||
|
||||
@staticmethod
|
||||
def get_history(student_modules):
|
||||
"""
|
||||
Find history objects across multiple backend stores for a given StudentModule
|
||||
"""
|
||||
|
||||
history_entries = []
|
||||
|
||||
if settings.FEATURES.get('ENABLE_CSMH_EXTENDED'):
|
||||
history_entries += coursewarehistoryextended.models.StudentModuleHistoryExtended.objects.filter(
|
||||
# Django will sometimes try to join to courseware_studentmodule
|
||||
# so just do an in query
|
||||
student_module__in=[module.id for module in student_modules]
|
||||
).order_by('-id')
|
||||
|
||||
# If we turn off reading from multiple history tables, then we don't want to read from
|
||||
# StudentModuleHistory anymore, we believe that all history is in the Extended table.
|
||||
if settings.FEATURES.get('ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES'):
|
||||
# we want to save later SQL queries on the model which allows us to prefetch
|
||||
history_entries += StudentModuleHistory.objects.prefetch_related('student_module').filter(
|
||||
student_module__in=student_modules
|
||||
).order_by('-id')
|
||||
|
||||
return history_entries
|
||||
|
||||
|
||||
class StudentModuleHistory(BaseStudentModuleHistory):
|
||||
"""Keeps a complete history of state changes for a given XModule for a given
|
||||
Student. Right now, we restrict this to problems so that the table doesn't
|
||||
explode in size."""
|
||||
|
||||
class Meta(object):
|
||||
app_label = "courseware"
|
||||
get_latest_by = "created"
|
||||
|
||||
student_module = models.ForeignKey(StudentModule, db_index=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(repr(self))
|
||||
|
||||
def save_history(sender, instance, **kwargs): # pylint: disable=no-self-argument, unused-argument
|
||||
"""
|
||||
Checks the instance's module_type, and creates & saves a
|
||||
StudentModuleHistory entry if the module_type is one that
|
||||
StudentModuleHistoryExtended entry if the module_type is one that
|
||||
we save.
|
||||
"""
|
||||
if instance.module_type in StudentModuleHistory.HISTORY_SAVING_TYPES:
|
||||
@@ -185,8 +230,11 @@ class StudentModuleHistory(models.Model):
|
||||
max_grade=instance.max_grade)
|
||||
history_entry.save()
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(repr(self))
|
||||
# When the extended studentmodulehistory table exists, don't save
|
||||
# duplicate history into courseware_studentmodulehistory, just retain
|
||||
# data for reading.
|
||||
if not settings.FEATURES.get('ENABLE_CSMH_EXTENDED'):
|
||||
post_save.connect(save_history, sender=StudentModule)
|
||||
|
||||
|
||||
class XBlockFieldBase(models.Model):
|
||||
|
||||
@@ -520,6 +520,7 @@ class UserRoleTestCase(TestCase):
|
||||
"""
|
||||
Tests for user roles.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(UserRoleTestCase, self).setUp()
|
||||
self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
|
||||
|
||||
@@ -240,6 +240,9 @@ class TestProgressSummary(TestCase):
|
||||
(2/5) (3/5) (0/1) - (1/3) - (3/10)
|
||||
|
||||
"""
|
||||
# Tell Django to clean out all databases, not just default
|
||||
multi_db = True
|
||||
|
||||
def setUp(self):
|
||||
super(TestProgressSummary, self).setUp()
|
||||
self.course_key = CourseLocator(
|
||||
|
||||
@@ -20,6 +20,7 @@ class BaseI18nTestCase(TestCase):
|
||||
"""
|
||||
Base utilities for i18n test classes to derive from
|
||||
"""
|
||||
|
||||
def assert_tag_has_attr(self, content, tag, attname, value):
|
||||
"""Assert that a tag in `content` has a certain value in a certain attribute."""
|
||||
regex = r"""<{tag} [^>]*\b{attname}=['"]([\w\d\- ]+)['"][^>]*>""".format(tag=tag, attname=attname)
|
||||
|
||||
@@ -103,6 +103,8 @@ class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
|
||||
"""Tests for user_state storage via StudentModule"""
|
||||
other_key_factory = partial(DjangoKeyValueStore.Key, Scope.user_state, 2, location('usage_id')) # user_id=2, not 1
|
||||
existing_field_name = "a_field"
|
||||
# Tell Django to clean out all databases, not just default
|
||||
multi_db = True
|
||||
|
||||
def setUp(self):
|
||||
super(TestStudentModuleStorage, self).setUp()
|
||||
@@ -137,8 +139,9 @@ class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
|
||||
# to discover if something other than the DjangoXBlockUserStateClient
|
||||
# has written to the StudentModule (such as UserStateCache setting the score
|
||||
# on the StudentModule).
|
||||
with self.assertNumQueries(3):
|
||||
self.kvs.set(user_state_key('a_field'), 'new_value')
|
||||
with self.assertNumQueries(2, using='default'):
|
||||
with self.assertNumQueries(1, using='student_module_history'):
|
||||
self.kvs.set(user_state_key('a_field'), 'new_value')
|
||||
self.assertEquals(1, StudentModule.objects.all().count())
|
||||
self.assertEquals({'b_field': 'b_value', 'a_field': 'new_value'}, json.loads(StudentModule.objects.all()[0].state))
|
||||
|
||||
@@ -149,8 +152,9 @@ class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
|
||||
# to discover if something other than the DjangoXBlockUserStateClient
|
||||
# has written to the StudentModule (such as UserStateCache setting the score
|
||||
# on the StudentModule).
|
||||
with self.assertNumQueries(3):
|
||||
self.kvs.set(user_state_key('not_a_field'), 'new_value')
|
||||
with self.assertNumQueries(2, using='default'):
|
||||
with self.assertNumQueries(1, using='student_module_history'):
|
||||
self.kvs.set(user_state_key('not_a_field'), 'new_value')
|
||||
self.assertEquals(1, StudentModule.objects.all().count())
|
||||
self.assertEquals({'b_field': 'b_value', 'a_field': 'a_value', 'not_a_field': 'new_value'}, json.loads(StudentModule.objects.all()[0].state))
|
||||
|
||||
@@ -161,8 +165,9 @@ class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
|
||||
# to discover if something other than the DjangoXBlockUserStateClient
|
||||
# has written to the StudentModule (such as UserStateCache setting the score
|
||||
# on the StudentModule).
|
||||
with self.assertNumQueries(3):
|
||||
self.kvs.delete(user_state_key('a_field'))
|
||||
with self.assertNumQueries(2, using='default'):
|
||||
with self.assertNumQueries(1, using='student_module_history'):
|
||||
self.kvs.delete(user_state_key('a_field'))
|
||||
self.assertEquals(1, StudentModule.objects.all().count())
|
||||
self.assertRaises(KeyError, self.kvs.get, user_state_key('not_a_field'))
|
||||
|
||||
@@ -201,8 +206,9 @@ class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
|
||||
# We also need to read the database to discover if something other than the
|
||||
# DjangoXBlockUserStateClient has written to the StudentModule (such as
|
||||
# UserStateCache setting the score on the StudentModule).
|
||||
with self.assertNumQueries(3):
|
||||
self.kvs.set_many(kv_dict)
|
||||
with self.assertNumQueries(2, using="default"):
|
||||
with self.assertNumQueries(1, using="student_module_history"):
|
||||
self.kvs.set_many(kv_dict)
|
||||
|
||||
for key in kv_dict:
|
||||
self.assertEquals(self.kvs.get(key), kv_dict[key])
|
||||
@@ -223,6 +229,9 @@ class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
|
||||
|
||||
@attr('shard_1')
|
||||
class TestMissingStudentModule(TestCase):
|
||||
# Tell Django to clean out all databases, not just default
|
||||
multi_db = True
|
||||
|
||||
def setUp(self):
|
||||
super(TestMissingStudentModule, self).setUp()
|
||||
|
||||
@@ -244,13 +253,15 @@ class TestMissingStudentModule(TestCase):
|
||||
self.assertEquals(0, len(self.field_data_cache))
|
||||
self.assertEquals(0, StudentModule.objects.all().count())
|
||||
|
||||
# We are updating a problem, so we write to courseware_studentmodulehistory
|
||||
# We are updating a problem, so we write to courseware_studentmodulehistoryextended
|
||||
# as well as courseware_studentmodule. We also need to read the database
|
||||
# to discover if something other than the DjangoXBlockUserStateClient
|
||||
# has written to the StudentModule (such as UserStateCache setting the score
|
||||
# on the StudentModule).
|
||||
with self.assertNumQueries(5):
|
||||
self.kvs.set(user_state_key('a_field'), 'a_value')
|
||||
# Django 1.8 also has a number of other BEGIN and SAVESTATE queries.
|
||||
with self.assertNumQueries(4, using='default'):
|
||||
with self.assertNumQueries(1, using='student_module_history'):
|
||||
self.kvs.set(user_state_key('a_field'), 'a_value')
|
||||
|
||||
self.assertEquals(1, sum(len(cache) for cache in self.field_data_cache.cache.values()))
|
||||
self.assertEquals(1, StudentModule.objects.all().count())
|
||||
|
||||
@@ -19,7 +19,7 @@ from capa.tests.response_xml_factory import (
|
||||
CodeResponseXMLFactory,
|
||||
)
|
||||
from courseware import grades
|
||||
from courseware.models import StudentModule, StudentModuleHistory
|
||||
from courseware.models import StudentModule, BaseStudentModuleHistory
|
||||
from courseware.tests.helpers import LoginEnrollmentTestCase
|
||||
from lms.djangoapps.lms_xblock.runtime import quote_slashes
|
||||
from student.tests.factories import UserFactory
|
||||
@@ -121,6 +121,8 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase, Probl
|
||||
Check that a course gets graded properly.
|
||||
"""
|
||||
|
||||
# Tell Django to clean out all databases, not just default
|
||||
multi_db = True
|
||||
# arbitrary constant
|
||||
COURSE_SLUG = "100"
|
||||
COURSE_NAME = "test_course"
|
||||
@@ -319,6 +321,9 @@ class TestCourseGrader(TestSubmittingProblems):
|
||||
"""
|
||||
Suite of tests for the course grader.
|
||||
"""
|
||||
# Tell Django to clean out all databases, not just default
|
||||
multi_db = True
|
||||
|
||||
def basic_setup(self, late=False, reset=False, showanswer=False):
|
||||
"""
|
||||
Set up a simple course for testing basic grading functionality.
|
||||
@@ -451,26 +456,20 @@ class TestCourseGrader(TestSubmittingProblems):
|
||||
self.submit_question_answer('p1', {'2_1': u'Correct'})
|
||||
|
||||
# Now fetch the state entry for that problem.
|
||||
student_module = StudentModule.objects.get(
|
||||
student_module = StudentModule.objects.filter(
|
||||
course_id=self.course.id,
|
||||
student=self.student_user
|
||||
)
|
||||
# count how many state history entries there are
|
||||
baseline = StudentModuleHistory.objects.filter(
|
||||
student_module=student_module
|
||||
)
|
||||
baseline_count = baseline.count()
|
||||
self.assertEqual(baseline_count, 3)
|
||||
baseline = BaseStudentModuleHistory.get_history(student_module)
|
||||
self.assertEqual(len(baseline), 3)
|
||||
|
||||
# now click "show answer"
|
||||
self.show_question_answer('p1')
|
||||
|
||||
# check that we don't have more state history entries
|
||||
csmh = StudentModuleHistory.objects.filter(
|
||||
student_module=student_module
|
||||
)
|
||||
current_count = csmh.count()
|
||||
self.assertEqual(current_count, 3)
|
||||
csmh = BaseStudentModuleHistory.get_history(student_module)
|
||||
self.assertEqual(len(csmh), 3)
|
||||
|
||||
def test_grade_with_max_score_cache(self):
|
||||
"""
|
||||
@@ -713,6 +712,8 @@ class TestCourseGrader(TestSubmittingProblems):
|
||||
@attr('shard_1')
|
||||
class ProblemWithUploadedFilesTest(TestSubmittingProblems):
|
||||
"""Tests of problems with uploaded files."""
|
||||
# Tell Django to clean out all databases, not just default
|
||||
multi_db = True
|
||||
|
||||
def setUp(self):
|
||||
super(ProblemWithUploadedFilesTest, self).setUp()
|
||||
@@ -768,6 +769,8 @@ class TestPythonGradedResponse(TestSubmittingProblems):
|
||||
"""
|
||||
Check that we can submit a schematic and custom response, and it answers properly.
|
||||
"""
|
||||
# Tell Django to clean out all databases, not just default
|
||||
multi_db = True
|
||||
|
||||
SCHEMATIC_SCRIPT = dedent("""
|
||||
# for a schematic response, submission[i] is the json representation
|
||||
|
||||
@@ -18,6 +18,8 @@ class TestDjangoUserStateClient(UserStateClientTestBase, TestCase):
|
||||
Tests of the DjangoUserStateClient backend.
|
||||
"""
|
||||
__test__ = True
|
||||
# Tell Django to clean out all databases, not just default
|
||||
multi_db = True
|
||||
|
||||
def _user(self, user_idx):
|
||||
return self.users[user_idx].username
|
||||
|
||||
@@ -15,7 +15,7 @@ except ImportError:
|
||||
import dogstats_wrapper as dog_stats_api
|
||||
from django.contrib.auth.models import User
|
||||
from xblock.fields import Scope, ScopeBase
|
||||
from courseware.models import StudentModule, StudentModuleHistory
|
||||
from courseware.models import StudentModule, BaseStudentModuleHistory
|
||||
from edx_user_state_client.interface import XBlockUserStateClient, XBlockUserState
|
||||
|
||||
|
||||
@@ -312,9 +312,7 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient):
|
||||
if len(student_modules) == 0:
|
||||
raise self.DoesNotExist()
|
||||
|
||||
history_entries = StudentModuleHistory.objects.prefetch_related('student_module').filter(
|
||||
student_module__in=student_modules
|
||||
).order_by('-id')
|
||||
history_entries = BaseStudentModuleHistory.get_history(student_modules)
|
||||
|
||||
# If no history records exist, raise an error
|
||||
if not history_entries:
|
||||
@@ -332,9 +330,9 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient):
|
||||
if state == {}:
|
||||
state = None
|
||||
|
||||
block_key = history_entry.student_module.module_state_key
|
||||
block_key = history_entry.csm.module_state_key
|
||||
block_key = block_key.map_into_course(
|
||||
history_entry.student_module.course_id
|
||||
history_entry.csm.course_id
|
||||
)
|
||||
|
||||
yield XBlockUserState(username, block_key, state, history_entry.created, scope)
|
||||
|
||||
@@ -59,7 +59,7 @@ from courseware.courses import (
|
||||
)
|
||||
from courseware.masquerade import setup_masquerade
|
||||
from courseware.model_data import FieldDataCache, ScoresClient
|
||||
from courseware.models import StudentModuleHistory
|
||||
from courseware.models import StudentModule, BaseStudentModuleHistory
|
||||
from courseware.url_helpers import get_redirect_url
|
||||
from courseware.user_state_client import DjangoXBlockUserStateClient
|
||||
from edxmako.shortcuts import render_to_response, render_to_string, marketing_link
|
||||
@@ -1173,11 +1173,12 @@ def submission_history(request, course_id, student_username, location):
|
||||
|
||||
# This is ugly, but until we have a proper submissions API that we can use to provide
|
||||
# the scores instead, it will have to do.
|
||||
scores = list(StudentModuleHistory.objects.filter(
|
||||
student_module__module_state_key=usage_key,
|
||||
student_module__student__username=student_username,
|
||||
student_module__course_id=course_key
|
||||
).order_by('-id'))
|
||||
csm = StudentModule.objects.filter(
|
||||
module_state_key=usage_key,
|
||||
student__username=student_username,
|
||||
course_id=course_key)
|
||||
|
||||
scores = BaseStudentModuleHistory.get_history(csm)
|
||||
|
||||
if len(scores) != len(history_entries):
|
||||
log.warning(
|
||||
|
||||
25
lms/djangoapps/coursewarehistoryextended/fields.py
Normal file
25
lms/djangoapps/coursewarehistoryextended/fields.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
Custom fields for use in the coursewarehistoryextended django app.
|
||||
"""
|
||||
|
||||
from django.db.models.fields import AutoField
|
||||
|
||||
|
||||
class UnsignedBigIntAutoField(AutoField):
|
||||
"""
|
||||
An unsigned 8-byte integer for auto-incrementing primary keys.
|
||||
"""
|
||||
def db_type(self, connection):
|
||||
if connection.settings_dict['ENGINE'] == 'django.db.backends.mysql':
|
||||
return "bigint UNSIGNED AUTO_INCREMENT"
|
||||
elif connection.settings_dict['ENGINE'] == 'django.db.backends.sqlite3':
|
||||
# Sqlite will only auto-increment the ROWID column. Any INTEGER PRIMARY KEY column
|
||||
# is an alias for that (https://www.sqlite.org/autoinc.html). An unsigned integer
|
||||
# isn't an alias for ROWID, so we have to give up on the unsigned part.
|
||||
return "integer"
|
||||
elif connection.settings_dict['ENGINE'] == 'django.db.backends.postgresql_psycopg2':
|
||||
# Pg's bigserial is implicitly unsigned (doesn't allow negative numbers) and
|
||||
# goes 1-9.2x10^18
|
||||
return "BIGSERIAL"
|
||||
else:
|
||||
return None
|
||||
@@ -0,0 +1,57 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import coursewarehistoryextended.fields
|
||||
from django.conf import settings
|
||||
|
||||
def bump_pk_start(apps, schema_editor):
|
||||
if not schema_editor.connection.alias == 'student_module_history':
|
||||
return
|
||||
StudentModuleHistory = apps.get_model("courseware", "StudentModuleHistory")
|
||||
biggest_id = StudentModuleHistory.objects.all().order_by('-id').first()
|
||||
initial_id = settings.STUDENTMODULEHISTORYEXTENDED_OFFSET
|
||||
if biggest_id is not None:
|
||||
initial_id += biggest_id.id
|
||||
|
||||
if schema_editor.connection.vendor == 'mysql':
|
||||
schema_editor.execute('ALTER TABLE coursewarehistoryextended_studentmodulehistoryextended AUTO_INCREMENT=%s', [initial_id])
|
||||
elif schema_editor.connection.vendor == 'sqlite3':
|
||||
# This is a hack to force sqlite to add new rows after the earlier rows we
|
||||
# want to migrate.
|
||||
StudentModuleHistory(
|
||||
id=initial_id,
|
||||
course_key=None,
|
||||
usage_key=None,
|
||||
username="",
|
||||
version="",
|
||||
created=datetime.datetime.now(),
|
||||
).save()
|
||||
elif schema_editor.connection.vendor == 'postgresql':
|
||||
schema_editor.execute("SELECT setval('coursewarehistoryextended_studentmodulehistoryextended_seq', %s)", [initial_id])
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('courseware', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='StudentModuleHistoryExtended',
|
||||
fields=[
|
||||
('version', models.CharField(db_index=True, max_length=255, null=True, blank=True)),
|
||||
('created', models.DateTimeField(db_index=True)),
|
||||
('state', models.TextField(null=True, blank=True)),
|
||||
('grade', models.FloatField(null=True, blank=True)),
|
||||
('max_grade', models.FloatField(null=True, blank=True)),
|
||||
('id', coursewarehistoryextended.fields.UnsignedBigIntAutoField(serialize=False, primary_key=True)),
|
||||
('student_module', models.ForeignKey(to='courseware.StudentModule', on_delete=django.db.models.deletion.DO_NOTHING, db_constraint=False)),
|
||||
],
|
||||
options={
|
||||
'get_latest_by': 'created',
|
||||
},
|
||||
),
|
||||
migrations.RunPython(bump_pk_start, reverse_code=migrations.RunPython.noop),
|
||||
]
|
||||
64
lms/djangoapps/coursewarehistoryextended/models.py
Normal file
64
lms/djangoapps/coursewarehistoryextended/models.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
WE'RE USING MIGRATIONS!
|
||||
|
||||
If you make changes to this model, be sure to create an appropriate migration
|
||||
file and check it in at the same time as your model changes. To do that,
|
||||
|
||||
1. Go to the edx-platform dir
|
||||
2. ./manage.py schemamigration courseware --auto description_of_your_change
|
||||
3. Add the migration file created in edx-platform/lms/djangoapps/coursewarehistoryextended/migrations/
|
||||
|
||||
|
||||
ASSUMPTIONS: modules have unique IDs, even across different module_types
|
||||
|
||||
"""
|
||||
from django.db import models
|
||||
from django.db.models.signals import post_save, post_delete
|
||||
from django.dispatch import receiver
|
||||
|
||||
from coursewarehistoryextended.fields import UnsignedBigIntAutoField
|
||||
from courseware.models import StudentModule, BaseStudentModuleHistory
|
||||
|
||||
|
||||
class StudentModuleHistoryExtended(BaseStudentModuleHistory):
|
||||
"""Keeps a complete history of state changes for a given XModule for a given
|
||||
Student. Right now, we restrict this to problems so that the table doesn't
|
||||
explode in size.
|
||||
|
||||
This new extended CSMH has a larger primary key that won't run out of space
|
||||
so quickly."""
|
||||
|
||||
class Meta(object):
|
||||
app_label = 'coursewarehistoryextended'
|
||||
get_latest_by = "created"
|
||||
|
||||
id = UnsignedBigIntAutoField(primary_key=True) # pylint: disable=invalid-name
|
||||
|
||||
student_module = models.ForeignKey(StudentModule, db_index=True, db_constraint=False, on_delete=models.DO_NOTHING)
|
||||
|
||||
@receiver(post_save, sender=StudentModule)
|
||||
def save_history(sender, instance, **kwargs): # pylint: disable=no-self-argument, unused-argument
|
||||
"""
|
||||
Checks the instance's module_type, and creates & saves a
|
||||
StudentModuleHistoryExtended entry if the module_type is one that
|
||||
we save.
|
||||
"""
|
||||
if instance.module_type in StudentModuleHistoryExtended.HISTORY_SAVING_TYPES:
|
||||
history_entry = StudentModuleHistoryExtended(student_module=instance,
|
||||
version=None,
|
||||
created=instance.modified,
|
||||
state=instance.state,
|
||||
grade=instance.grade,
|
||||
max_grade=instance.max_grade)
|
||||
history_entry.save()
|
||||
|
||||
@receiver(post_delete, sender=StudentModule)
|
||||
def delete_history(sender, instance, **kwargs): # pylint: disable=no-self-argument, unused-argument
|
||||
"""
|
||||
Django can't cascade delete across databases, so we tell it at the model level to
|
||||
on_delete=DO_NOTHING and then listen for post_delete so we can clean up the CSMHE rows.
|
||||
"""
|
||||
StudentModuleHistoryExtended.objects.filter(student_module=instance).all().delete()
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(repr(self))
|
||||
81
lms/djangoapps/coursewarehistoryextended/tests.py
Normal file
81
lms/djangoapps/coursewarehistoryextended/tests.py
Normal file
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
Tests for coursewarehistoryextended
|
||||
Many aspects of this app are covered by the courseware tests,
|
||||
but these are specific to the new storage model with multiple
|
||||
backend tables.
|
||||
"""
|
||||
|
||||
import json
|
||||
from mock import patch
|
||||
from django.test import TestCase
|
||||
from django.conf import settings
|
||||
from unittest import skipUnless
|
||||
from nose.plugins.attrib import attr
|
||||
|
||||
from courseware.models import BaseStudentModuleHistory, StudentModuleHistory, StudentModule
|
||||
|
||||
from courseware.tests.factories import StudentModuleFactory, location, course_id
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
@skipUnless(settings.FEATURES["ENABLE_CSMH_EXTENDED"], "CSMH Extended needs to be enabled")
|
||||
class TestStudentModuleHistoryBackends(TestCase):
|
||||
""" Tests of data in CSMH and CSMHE """
|
||||
# Tell Django to clean out all databases, not just default
|
||||
multi_db = True
|
||||
|
||||
def setUp(self):
|
||||
super(TestStudentModuleHistoryBackends, self).setUp()
|
||||
for record in (1, 2, 3):
|
||||
# This will store into CSMHE via the post_save signal
|
||||
csm = StudentModuleFactory.create(module_state_key=location('usage_id'),
|
||||
course_id=course_id,
|
||||
state=json.dumps({'type': 'csmhe', 'order': record}))
|
||||
# This manually gets us a CSMH record to compare
|
||||
csmh = StudentModuleHistory(student_module=csm,
|
||||
version=None,
|
||||
created=csm.modified,
|
||||
state=json.dumps({'type': 'csmh', 'order': record}),
|
||||
grade=csm.grade,
|
||||
max_grade=csm.max_grade)
|
||||
csmh.save()
|
||||
|
||||
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_CSMH_EXTENDED": True})
|
||||
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES": True})
|
||||
def test_get_history_true_true(self):
|
||||
student_module = StudentModule.objects.all()
|
||||
history = BaseStudentModuleHistory.get_history(student_module)
|
||||
self.assertEquals(len(history), 6)
|
||||
self.assertEquals({'type': 'csmhe', 'order': 3}, json.loads(history[0].state))
|
||||
self.assertEquals({'type': 'csmhe', 'order': 2}, json.loads(history[1].state))
|
||||
self.assertEquals({'type': 'csmhe', 'order': 1}, json.loads(history[2].state))
|
||||
self.assertEquals({'type': 'csmh', 'order': 3}, json.loads(history[3].state))
|
||||
self.assertEquals({'type': 'csmh', 'order': 2}, json.loads(history[4].state))
|
||||
self.assertEquals({'type': 'csmh', 'order': 1}, json.loads(history[5].state))
|
||||
|
||||
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_CSMH_EXTENDED": True})
|
||||
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES": False})
|
||||
def test_get_history_true_false(self):
|
||||
student_module = StudentModule.objects.all()
|
||||
history = BaseStudentModuleHistory.get_history(student_module)
|
||||
self.assertEquals(len(history), 3)
|
||||
self.assertEquals({'type': 'csmhe', 'order': 3}, json.loads(history[0].state))
|
||||
self.assertEquals({'type': 'csmhe', 'order': 2}, json.loads(history[1].state))
|
||||
self.assertEquals({'type': 'csmhe', 'order': 1}, json.loads(history[2].state))
|
||||
|
||||
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_CSMH_EXTENDED": False})
|
||||
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES": True})
|
||||
def test_get_history_false_true(self):
|
||||
student_module = StudentModule.objects.all()
|
||||
history = BaseStudentModuleHistory.get_history(student_module)
|
||||
self.assertEquals(len(history), 3)
|
||||
self.assertEquals({'type': 'csmh', 'order': 3}, json.loads(history[0].state))
|
||||
self.assertEquals({'type': 'csmh', 'order': 2}, json.loads(history[1].state))
|
||||
self.assertEquals({'type': 'csmh', 'order': 1}, json.loads(history[2].state))
|
||||
|
||||
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_CSMH_EXTENDED": False})
|
||||
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES": False})
|
||||
def test_get_history_false_false(self):
|
||||
student_module = StudentModule.objects.all()
|
||||
history = BaseStudentModuleHistory.get_history(student_module)
|
||||
self.assertEquals(len(history), 0)
|
||||
@@ -72,6 +72,14 @@ DATABASES = {
|
||||
'timeout': 30,
|
||||
},
|
||||
'ATOMIC_REQUESTS': True,
|
||||
},
|
||||
'student_module_history': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': TEST_ROOT / "db" / "test_student_module_history.db",
|
||||
'TEST_NAME': TEST_ROOT / "db" / "test_student_module_history.db",
|
||||
'OPTIONS': {
|
||||
'timeout': 30,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +153,9 @@ LETTUCE_APPS = ('courseware', 'instructor')
|
||||
# This causes some pretty cryptic errors as lettuce tries
|
||||
# to parse files in `instructor_task` as features.
|
||||
# As a quick workaround, explicitly exclude the `instructor_task` app.
|
||||
LETTUCE_AVOID_APPS = ('instructor_task',)
|
||||
# The coursewarehistoryextended app also falls prey to this fuzzy
|
||||
# for the courseware app.
|
||||
LETTUCE_AVOID_APPS = ('instructor_task', 'coursewarehistoryextended')
|
||||
|
||||
LETTUCE_BROWSER = os.environ.get('LETTUCE_BROWSER', 'chrome')
|
||||
|
||||
|
||||
@@ -759,6 +759,11 @@ MICROSITE_DATABASE_TEMPLATE_CACHE_TTL = ENV_TOKENS.get(
|
||||
# Course Content Bookmarks Settings
|
||||
MAX_BOOKMARKS_PER_COURSE = ENV_TOKENS.get('MAX_BOOKMARKS_PER_COURSE', MAX_BOOKMARKS_PER_COURSE)
|
||||
|
||||
# Offset for pk of courseware.StudentModuleHistoryExtended
|
||||
STUDENTMODULEHISTORYEXTENDED_OFFSET = ENV_TOKENS.get(
|
||||
'STUDENTMODULEHISTORYEXTENDED_OFFSET', STUDENTMODULEHISTORYEXTENDED_OFFSET
|
||||
)
|
||||
|
||||
# Cutoff date for granting audit certificates
|
||||
if ENV_TOKENS.get('AUDIT_CERT_CUTOFF_DATE', None):
|
||||
AUDIT_CERT_CUTOFF_DATE = dateutil.parser.parse(ENV_TOKENS.get('AUDIT_CERT_CUTOFF_DATE'))
|
||||
@@ -766,3 +771,7 @@ if ENV_TOKENS.get('AUDIT_CERT_CUTOFF_DATE', None):
|
||||
################################ Settings for Credentials Service ################################
|
||||
|
||||
CREDENTIALS_GENERATION_ROUTING_KEY = HIGH_PRIORITY_QUEUE
|
||||
|
||||
# The extended StudentModule history table
|
||||
if FEATURES.get('ENABLE_CSMH_EXTENDED'):
|
||||
INSTALLED_APPS += ('coursewarehistoryextended',)
|
||||
|
||||
@@ -13,18 +13,25 @@ from .aws import *
|
||||
import os
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
DB_OVERRIDES = dict(
|
||||
PASSWORD=os.environ.get('DB_MIGRATION_PASS', None),
|
||||
ENGINE=os.environ.get('DB_MIGRATION_ENGINE', DATABASES['default']['ENGINE']),
|
||||
USER=os.environ.get('DB_MIGRATION_USER', DATABASES['default']['USER']),
|
||||
NAME=os.environ.get('DB_MIGRATION_NAME', DATABASES['default']['NAME']),
|
||||
HOST=os.environ.get('DB_MIGRATION_HOST', DATABASES['default']['HOST']),
|
||||
PORT=os.environ.get('DB_MIGRATION_PORT', DATABASES['default']['PORT']),
|
||||
)
|
||||
|
||||
if DB_OVERRIDES['PASSWORD'] is None:
|
||||
raise ImproperlyConfigured("No database password was provided for running "
|
||||
"migrations. This is fatal.")
|
||||
def get_db_overrides(db_name):
|
||||
"""
|
||||
Now that we have multiple databases, we want to look up from the environment
|
||||
for both databases.
|
||||
"""
|
||||
db_overrides = dict(
|
||||
PASSWORD=os.environ.get('DB_MIGRATION_PASS', None),
|
||||
ENGINE=os.environ.get('DB_MIGRATION_ENGINE', DATABASES[db_name]['ENGINE']),
|
||||
USER=os.environ.get('DB_MIGRATION_USER', DATABASES[db_name]['USER']),
|
||||
NAME=os.environ.get('DB_MIGRATION_NAME', DATABASES[db_name]['NAME']),
|
||||
HOST=os.environ.get('DB_MIGRATION_HOST', DATABASES[db_name]['HOST']),
|
||||
PORT=os.environ.get('DB_MIGRATION_PORT', DATABASES[db_name]['PORT']),
|
||||
)
|
||||
|
||||
for override, value in DB_OVERRIDES.iteritems():
|
||||
DATABASES['default'][override] = value
|
||||
if db_overrides['PASSWORD'] is None:
|
||||
raise ImproperlyConfigured("No database password was provided for running "
|
||||
"migrations. This is fatal.")
|
||||
return db_overrides
|
||||
|
||||
for db in ['default', 'student_module_history']:
|
||||
DATABASES[db].update(get_db_overrides(db))
|
||||
|
||||
@@ -39,6 +39,14 @@
|
||||
"PASSWORD": "",
|
||||
"PORT": "3306",
|
||||
"USER": "root"
|
||||
},
|
||||
"student_module_history": {
|
||||
"ENGINE": "django.db.backends.mysql",
|
||||
"HOST": "localhost",
|
||||
"NAME": "student_module_history_test",
|
||||
"PASSWORD": "",
|
||||
"PORT": "3306",
|
||||
"USER": "root"
|
||||
}
|
||||
},
|
||||
"DOC_STORE_CONFIG": {
|
||||
|
||||
@@ -178,6 +178,11 @@ PROFILE_IMAGE_BACKEND = {
|
||||
'base_url': os.path.join(MEDIA_URL, 'profile-images/'),
|
||||
},
|
||||
}
|
||||
|
||||
# Make sure we test with the extended history table
|
||||
FEATURES['ENABLE_CSMH_EXTENDED'] = True
|
||||
INSTALLED_APPS += ('coursewarehistoryextended',)
|
||||
|
||||
#####################################################################
|
||||
# Lastly, see if the developer has any local overrides.
|
||||
try:
|
||||
|
||||
@@ -363,6 +363,18 @@ FEATURES = {
|
||||
|
||||
# Show Language selector.
|
||||
'SHOW_LANGUAGE_SELECTOR': False,
|
||||
|
||||
# Write new CSM history to the extended table.
|
||||
# This will eventually default to True and may be
|
||||
# removed since all installs should have the separate
|
||||
# extended history table.
|
||||
'ENABLE_CSMH_EXTENDED': False,
|
||||
|
||||
# Read from both the CSMH and CSMHE history tables.
|
||||
# This is the default, but can be disabled if all history
|
||||
# lives in the Extended table, saving the frontend from
|
||||
# making multiple queries.
|
||||
'ENABLE_READING_FROM_MULTIPLE_HISTORY_TABLES': True
|
||||
}
|
||||
|
||||
# Ignore static asset files on import which match this pattern
|
||||
@@ -413,6 +425,12 @@ GEOIPV6_PATH = REPO_ROOT / "common/static/data/geoip/GeoIPv6.dat"
|
||||
# Where to look for a status message
|
||||
STATUS_MESSAGE_PATH = ENV_ROOT / "status_message.json"
|
||||
|
||||
############################ Global Database Configuration #####################
|
||||
|
||||
DATABASE_ROUTERS = [
|
||||
'openedx.core.lib.django_courseware_routers.StudentModuleHistoryExtendedRouter',
|
||||
]
|
||||
|
||||
############################ OpenID Provider ##################################
|
||||
OPENID_PROVIDER_TRUSTED_ROOTS = ['cs50.net', '*.cs50.net']
|
||||
|
||||
@@ -2759,6 +2777,13 @@ MOBILE_APP_USER_AGENT_REGEXES = [
|
||||
r'edX/org.edx.mobile',
|
||||
]
|
||||
|
||||
# Offset for courseware.StudentModuleHistoryExtended which is used to
|
||||
# calculate the starting primary key for the underlying table. This gap
|
||||
# should be large enough that you do not generate more than N courseware.StudentModuleHistory
|
||||
# records before you have deployed the app to write to coursewarehistoryextended.StudentModuleHistoryExtended
|
||||
# if you want to avoid an overlap in ids while searching for history across the two tables.
|
||||
STUDENTMODULEHISTORYEXTENDED_OFFSET = 10000
|
||||
|
||||
# Deprecated xblock types
|
||||
DEPRECATED_ADVANCED_COMPONENT_TYPES = []
|
||||
|
||||
|
||||
@@ -48,6 +48,11 @@ DATABASES = {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': ENV_ROOT / "db" / "edx.db",
|
||||
'ATOMIC_REQUESTS': True,
|
||||
},
|
||||
'student_module_history': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': ENV_ROOT / "db" / "student_module_history.db",
|
||||
'ATOMIC_REQUESTS': True,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,15 @@ DATABASES = {
|
||||
'HOST': '127.0.0.1',
|
||||
'PORT': '3306',
|
||||
'ATOMIC_REQUESTS': True,
|
||||
},
|
||||
'student_module_history': {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'student_module_history',
|
||||
'USER': 'root',
|
||||
'PASSWORD': '',
|
||||
'HOST': '127.0.0.1',
|
||||
'PORT': '3306',
|
||||
'ATOMIC_REQUESTS': True,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,11 @@ DATABASES = {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': ENV_ROOT / "db" / "edx.db",
|
||||
'ATOMIC_REQUESTS': True,
|
||||
},
|
||||
'student_module_history': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': ENV_ROOT / "db" / "student_module_history.db",
|
||||
'ATOMIC_REQUESTS': True,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +186,10 @@ DATABASES = {
|
||||
'NAME': TEST_ROOT / 'db' / 'edx.db',
|
||||
'ATOMIC_REQUESTS': True,
|
||||
},
|
||||
|
||||
'student_module_history': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': TEST_ROOT / 'db' / 'student_module_history.db'
|
||||
},
|
||||
}
|
||||
|
||||
if os.environ.get('DISABLE_MIGRATIONS'):
|
||||
@@ -194,6 +197,10 @@ if os.environ.get('DISABLE_MIGRATIONS'):
|
||||
# to Django 1.9, which allows setting MIGRATION_MODULES to None in order to skip migrations.
|
||||
MIGRATION_MODULES = NoOpMigrationModules()
|
||||
|
||||
# Make sure we test with the extended history table
|
||||
FEATURES['ENABLE_CSMH_EXTENDED'] = True
|
||||
INSTALLED_APPS += ('coursewarehistoryextended',)
|
||||
|
||||
CACHES = {
|
||||
# This is the cache used for most things.
|
||||
# In staging/prod envs, the sessions also live here.
|
||||
|
||||
@@ -19,7 +19,9 @@ DATABASES = {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'ATOMIC_REQUESTS': True,
|
||||
},
|
||||
|
||||
'student_module_history': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
},
|
||||
}
|
||||
|
||||
# Provide a dummy XQUEUE_INTERFACE setting as LMS expects it to exist on start up
|
||||
|
||||
57
openedx/core/lib/django_courseware_routers.py
Normal file
57
openedx/core/lib/django_courseware_routers.py
Normal file
@@ -0,0 +1,57 @@
|
||||
"""
|
||||
Database Routers for use with the coursewarehistoryextended django app.
|
||||
"""
|
||||
|
||||
|
||||
class StudentModuleHistoryExtendedRouter(object):
|
||||
"""
|
||||
A Database Router that separates StudentModuleHistoryExtended into its own database.
|
||||
"""
|
||||
|
||||
DATABASE_NAME = 'student_module_history'
|
||||
|
||||
def _is_csmh(self, model):
|
||||
"""
|
||||
Return True if ``model`` is courseware.StudentModuleHistoryExtended.
|
||||
"""
|
||||
return (
|
||||
model._meta.app_label == 'coursewarehistoryextended' and # pylint: disable=protected-access
|
||||
model.__name__ == 'StudentModuleHistoryExtended'
|
||||
)
|
||||
|
||||
def db_for_read(self, model, **hints): # pylint: disable=unused-argument
|
||||
"""
|
||||
Use the StudentModuleHistoryExtendedRouter.DATABASE_NAME if the model is StudentModuleHistoryExtended.
|
||||
"""
|
||||
if self._is_csmh(model):
|
||||
return self.DATABASE_NAME
|
||||
else:
|
||||
return None
|
||||
|
||||
def db_for_write(self, model, **hints): # pylint: disable=unused-argument
|
||||
"""
|
||||
Use the StudentModuleHistoryExtendedRouter.DATABASE_NAME if the model is StudentModuleHistoryExtended.
|
||||
"""
|
||||
if self._is_csmh(model):
|
||||
return self.DATABASE_NAME
|
||||
else:
|
||||
return None
|
||||
|
||||
def allow_relation(self, obj1, obj2, **hints): # pylint: disable=unused-argument
|
||||
"""
|
||||
Disable relations if the model is StudentModuleHistoryExtended.
|
||||
"""
|
||||
if self._is_csmh(obj1) or self._is_csmh(obj2):
|
||||
return False
|
||||
return None
|
||||
|
||||
def allow_migrate(self, db, model): # pylint: disable=unused-argument
|
||||
"""
|
||||
Only sync StudentModuleHistoryExtended to StudentModuleHistoryExtendedRouter.DATABASE_Name
|
||||
"""
|
||||
if self._is_csmh(model):
|
||||
return db == self.DATABASE_NAME
|
||||
elif db == self.DATABASE_NAME:
|
||||
return False
|
||||
|
||||
return None
|
||||
@@ -69,8 +69,14 @@ class AcceptanceTestSuite(TestSuite):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AcceptanceTestSuite, self).__init__(*args, **kwargs)
|
||||
self.root = 'acceptance'
|
||||
self.db = Env.REPO_ROOT / 'test_root/db/test_edx.db'
|
||||
self.db_cache = Env.REPO_ROOT / 'common/test/db_cache/lettuce.db'
|
||||
self.dbs = {
|
||||
'default': Env.REPO_ROOT / 'test_root/db/test_edx.db',
|
||||
'student_module_history': Env.REPO_ROOT / 'test_root/db/test_student_module_history.db'
|
||||
}
|
||||
self.db_caches = {
|
||||
'default': Env.REPO_ROOT / 'common/test/db_cache/lettuce.db',
|
||||
'student_module_history': Env.REPO_ROOT / 'common/test/db_cache/lettuce_student_module_history.db'
|
||||
}
|
||||
self.fasttest = kwargs.get('fasttest', False)
|
||||
|
||||
if kwargs.get('system'):
|
||||
@@ -114,24 +120,30 @@ class AcceptanceTestSuite(TestSuite):
|
||||
definitions to sync and migrate.
|
||||
"""
|
||||
|
||||
if self.db.isfile():
|
||||
# Since we are using SQLLite, we can reset the database by deleting it on disk.
|
||||
self.db.remove()
|
||||
for db in self.dbs.keys():
|
||||
if self.dbs[db].isfile():
|
||||
# Since we are using SQLLite, we can reset the database by deleting it on disk.
|
||||
self.dbs[db].remove()
|
||||
|
||||
if self.db_cache.isfile():
|
||||
if all(self.db_caches[cache].isfile() for cache in self.db_caches.keys()):
|
||||
# To speed up migrations, we check for a cached database file and start from that.
|
||||
# The cached database file should be checked into the repo
|
||||
|
||||
# Copy the cached database to the test root directory
|
||||
sh("cp {db_cache} {db}".format(db_cache=self.db_cache, db=self.db))
|
||||
for db_alias in self.dbs.keys():
|
||||
sh("cp {db_cache} {db}".format(db_cache=self.db_caches[db_alias], db=self.dbs[db_alias]))
|
||||
|
||||
# Run migrations to update the db, starting from its cached state
|
||||
sh("./manage.py lms --settings acceptance migrate --traceback --noinput --fake-initial")
|
||||
sh("./manage.py cms --settings acceptance migrate --traceback --noinput --fake-initial")
|
||||
for db_alias in sorted(self.dbs.keys()):
|
||||
# pylint: disable=line-too-long
|
||||
sh("./manage.py lms --settings acceptance migrate --traceback --noinput --fake-initial --database {}".format(db_alias))
|
||||
sh("./manage.py cms --settings acceptance migrate --traceback --noinput --fake-initial --database {}".format(db_alias))
|
||||
else:
|
||||
# If no cached database exists, syncdb before migrating, then create the cache
|
||||
sh("./manage.py lms --settings acceptance migrate --traceback --noinput")
|
||||
sh("./manage.py cms --settings acceptance migrate --traceback --noinput")
|
||||
for db_alias in sorted(self.dbs.keys()):
|
||||
sh("./manage.py lms --settings acceptance migrate --traceback --noinput --database {}".format(db_alias))
|
||||
sh("./manage.py cms --settings acceptance migrate --traceback --noinput --database {}".format(db_alias))
|
||||
|
||||
# Create the cache if it doesn't already exist
|
||||
sh("cp {db} {db_cache}".format(db_cache=self.db_cache, db=self.db))
|
||||
for db_alias in self.dbs.keys():
|
||||
sh("cp {db} {db_cache}".format(db_cache=self.db_caches[db_alias], db=self.dbs[db_alias]))
|
||||
|
||||
@@ -24,35 +24,55 @@
|
||||
|
||||
DB_CACHE_DIR="common/test/db_cache"
|
||||
|
||||
# Ensure the test database exists.
|
||||
echo "CREATE DATABASE IF NOT EXISTS edxtest;" | mysql -u root
|
||||
declare -A databases
|
||||
declare -a database_order
|
||||
databases=(["default"]="edxtest" ["student_module_history"]="student_module_history_test")
|
||||
database_order=("default" "student_module_history")
|
||||
|
||||
# Clear out the test database
|
||||
#
|
||||
# We are using the django-extensions's reset_db command which uses "DROP DATABASE" and
|
||||
# "CREATE DATABASE" in case the tests are being run in an environment (e.g. devstack
|
||||
# or a jenkins worker environment) that already ran tests on another commit that had
|
||||
# different migrations that created, dropped, or altered tables.
|
||||
echo "Issuing a reset_db command to the bok_choy MySQL database."
|
||||
./manage.py lms --settings bok_choy reset_db --traceback --noinput
|
||||
# Ensure the test database exists.
|
||||
for db in "${database_order[@]}"; do
|
||||
echo "CREATE DATABASE IF NOT EXISTS ${databases[$db]};" | mysql -u root
|
||||
|
||||
# Clear out the test database
|
||||
#
|
||||
# We are using the django-extensions's reset_db command which uses "DROP DATABASE" and
|
||||
# "CREATE DATABASE" in case the tests are being run in an environment (e.g. devstack
|
||||
# or a jenkins worker environment) that already ran tests on another commit that had
|
||||
# different migrations that created, dropped, or altered tables.
|
||||
echo "Issuing a reset_db command to the $db bok_choy MySQL database."
|
||||
./manage.py lms --settings bok_choy reset_db --traceback --noinput --router $db
|
||||
|
||||
# If there are cached database schemas/data, load them
|
||||
if [[ ! -f $DB_CACHE_DIR/bok_choy_schema_$db.sql || ! -f $DB_CACHE_DIR/bok_choy_data_$db.json || ! -f $DB_CACHE_DIR/bok_choy_migrations_data_$db.sql ]]; then
|
||||
echo "Missing $DB_CACHE_DIR/bok_choy_schema_$db.sql or $DB_CACHE_DIR/bok_choy_data_$db.json, or $DB_CACHE_DIR/bok_choy_migrations_data_$db.sql rebuilding cache"
|
||||
REBUILD_CACHE=true
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
# If there are cached database schemas/data, load them
|
||||
if [[ -f $DB_CACHE_DIR/bok_choy_schema.sql && -f $DB_CACHE_DIR/bok_choy_migrations_data.sql && -f $DB_CACHE_DIR/bok_choy_data.json ]]; then
|
||||
if [[ -z $REBUILD_CACHE ]]; then
|
||||
|
||||
echo "Found the bok_choy DB cache files. Loading them into the database..."
|
||||
# Load the schema, then the data (including the migration history)
|
||||
echo "Loading the schema from the filesystem into the MySQL DB."
|
||||
mysql -u root edxtest < $DB_CACHE_DIR/bok_choy_schema.sql
|
||||
echo "Loading the migration data from the filesystem into the MySQL DB."
|
||||
mysql -u root edxtest < $DB_CACHE_DIR/bok_choy_migrations_data.sql
|
||||
echo "Loading the fixture data from the filesystem into the MySQL DB."
|
||||
./manage.py lms --settings bok_choy loaddata $DB_CACHE_DIR/bok_choy_data.json
|
||||
|
||||
# Re-run migrations to ensure we are up-to-date
|
||||
echo "Running the lms migrations on the bok_choy DB."
|
||||
./manage.py lms --settings bok_choy migrate --traceback --noinput
|
||||
echo "Running the cms migrations on the bok_choy DB."
|
||||
./manage.py cms --settings bok_choy migrate --traceback --noinput
|
||||
for db in "${database_order[@]}"; do
|
||||
# Load the schema, then the data (including the migration history)
|
||||
echo "Loading the schema from the filesystem into the $db MySQL DB."
|
||||
mysql -u root "${databases["$db"]}" < $DB_CACHE_DIR/bok_choy_schema_$db.sql
|
||||
echo "Loading the fixture data from the filesystem into the $db MySQL DB."
|
||||
./manage.py lms --settings bok_choy loaddata --database $db $DB_CACHE_DIR/bok_choy_data_$db.json
|
||||
|
||||
# Migrations are stored in the default database
|
||||
echo "Loading the migration data from the filesystem into the $db MySQL DB."
|
||||
mysql -u root "${databases["$db"]}" < $DB_CACHE_DIR/bok_choy_migrations_data_$db.sql
|
||||
|
||||
# Re-run migrations to ensure we are up-to-date
|
||||
echo "Running the lms migrations on the $db bok_choy DB."
|
||||
./manage.py lms --settings bok_choy migrate --database $db --traceback --noinput
|
||||
echo "Running the cms migrations on the $db bok_choy DB."
|
||||
./manage.py cms --settings bok_choy migrate --database $db --traceback --noinput
|
||||
|
||||
done
|
||||
|
||||
# Otherwise, update the test database and update the cache
|
||||
else
|
||||
@@ -60,19 +80,21 @@ else
|
||||
# Clean the cache directory
|
||||
mkdir -p $DB_CACHE_DIR && rm -f $DB_CACHE_DIR/bok_choy*
|
||||
|
||||
# Re-run migrations on the test database
|
||||
echo "Issuing a migrate command to the bok_choy MySQL database for the lms django apps."
|
||||
./manage.py lms --settings bok_choy migrate --traceback --noinput
|
||||
echo "Issuing a migrate command to the bok_choy MySQL database for the cms django apps."
|
||||
./manage.py cms --settings bok_choy migrate --traceback --noinput
|
||||
for db in "${database_order[@]}"; do
|
||||
# Re-run migrations on the test database
|
||||
echo "Issuing a migrate command to the $db bok_choy MySQL database for the lms django apps."
|
||||
./manage.py lms --settings bok_choy migrate --database $db --traceback --noinput
|
||||
echo "Issuing a migrate command to the $db bok_choy MySQL database for the cms django apps."
|
||||
./manage.py cms --settings bok_choy migrate --database $db --traceback --noinput
|
||||
|
||||
# Dump the schema and data to the cache
|
||||
echo "Using the dumpdata command to save the fixture data to the filesystem."
|
||||
./manage.py lms --settings bok_choy dumpdata > $DB_CACHE_DIR/bok_choy_data.json
|
||||
# dump_data does not dump the django_migrations table so we do it separately.
|
||||
echo "Saving the django_migrations table of the bok_choy DB to the filesystem."
|
||||
mysqldump -u root --no-create-info edxtest django_migrations > $DB_CACHE_DIR/bok_choy_migrations_data.sql
|
||||
echo "Saving the schema of the bok_choy DB to the filesystem."
|
||||
mysqldump -u root --no-data --skip-comments --skip-dump-date edxtest > $DB_CACHE_DIR/bok_choy_schema.sql
|
||||
# Dump the schema and data to the cache
|
||||
echo "Using the dumpdata command to save the $db fixture data to the filesystem."
|
||||
./manage.py lms --settings bok_choy dumpdata --database $db > $DB_CACHE_DIR/bok_choy_data_$db.json
|
||||
echo "Saving the schema of the $dh bok_choy DB to the filesystem."
|
||||
mysqldump -u root --no-data --skip-comments --skip-dump-date "${databases[$db]}" > $DB_CACHE_DIR/bok_choy_schema_$db.sql
|
||||
|
||||
# dump_data does not dump the django_migrations table so we do it separately.
|
||||
echo "Saving the django_migrations table of the $db bok_choy DB to the filesystem."
|
||||
mysqldump -u root --no-create-info "${databases["$db"]}" django_migrations > $DB_CACHE_DIR/bok_choy_migrations_data_$db.sql
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user