Commit 899ea6e0 by Will Daly

Use the read replica in the download data management command.

parent f4661727
...@@ -31,6 +31,8 @@ pip-log.txt ...@@ -31,6 +31,8 @@ pip-log.txt
nosetests.xml nosetests.xml
htmlcov htmlcov
coverage.xml coverage.xml
test_ora2db
test_ora2db-journal
# Mr Developer # Mr Developer
.mr.developer.cfg .mr.developer.cfg
......
...@@ -8,5 +8,11 @@ if __name__ == "__main__": ...@@ -8,5 +8,11 @@ if __name__ == "__main__":
if os.environ.get('DJANGO_SETTINGS_MODULE') is None: if os.environ.get('DJANGO_SETTINGS_MODULE') is None:
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings.dev' os.environ['DJANGO_SETTINGS_MODULE'] = 'settings.dev'
# When using an on-disk database for the test suite,
# Django asks us if we want to delete the database.
# We do.
if 'test' in sys.argv[0:3]:
sys.argv.append('--noinput')
from django.core.management import execute_from_command_line from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv) execute_from_command_line(sys.argv)
...@@ -3,6 +3,7 @@ Aggregate data for openassessment. ...@@ -3,6 +3,7 @@ Aggregate data for openassessment.
""" """
import csv import csv
import json import json
from django.conf import settings
from submissions import api as sub_api from submissions import api as sub_api
from openassessment.workflow.models import AssessmentWorkflow from openassessment.workflow.models import AssessmentWorkflow
from openassessment.assessment.models import AssessmentPart, AssessmentFeedback from openassessment.assessment.models import AssessmentPart, AssessmentFeedback
...@@ -110,14 +111,18 @@ class CsvWriter(object): ...@@ -110,14 +111,18 @@ class CsvWriter(object):
# Django 1.4 doesn't follow reverse relations when using select_related, # Django 1.4 doesn't follow reverse relations when using select_related,
# so we select AssessmentPart and follow the foreign key to the Assessment. # so we select AssessmentPart and follow the foreign key to the Assessment.
parts = AssessmentPart.objects.select_related( parts = self._use_read_replica(
'assessment', 'option', 'option__criterion' AssessmentPart.objects.select_related('assessment', 'option', 'option__criterion')
).filter(assessment__submission_uuid=submission_uuid).order_by('assessment__pk') .filter(assessment__submission_uuid=submission_uuid)
.order_by('assessment__pk')
)
self._write_assessment_to_csv(parts, rubric_points_cache) self._write_assessment_to_csv(parts, rubric_points_cache)
feedback_query = AssessmentFeedback.objects.filter( feedback_query = self._use_read_replica(
submission_uuid=submission_uuid AssessmentFeedback.objects
).prefetch_related('options') .filter(submission_uuid=submission_uuid)
.prefetch_related('options')
)
for assessment_feedback in feedback_query: for assessment_feedback in feedback_query:
self._write_assessment_feedback_to_csv(assessment_feedback) self._write_assessment_feedback_to_csv(assessment_feedback)
feedback_option_set.update(set( feedback_option_set.update(set(
...@@ -146,8 +151,8 @@ class CsvWriter(object): ...@@ -146,8 +151,8 @@ class CsvWriter(object):
""" """
num_results = 0 num_results = 0
start = 0 start = 0
total_results = AssessmentWorkflow.objects.filter( total_results = self._use_read_replica(
course_id=course_id AssessmentWorkflow.objects.filter(course_id=course_id)
).count() ).count()
while num_results < total_results: while num_results < total_results:
...@@ -156,9 +161,11 @@ class CsvWriter(object): ...@@ -156,9 +161,11 @@ class CsvWriter(object):
# so if we counted N at the start of the loop, # so if we counted N at the start of the loop,
# there should be >= N for us to process. # there should be >= N for us to process.
end = start + self.QUERY_INTERVAL end = start + self.QUERY_INTERVAL
query = AssessmentWorkflow.objects.filter( query = self._use_read_replica(
course_id=course_id AssessmentWorkflow.objects
).order_by('created').values('submission_uuid')[start:end] .filter(course_id=course_id)
.order_by('created')
).values('submission_uuid')[start:end]
for workflow_dict in query: for workflow_dict in query:
num_results += 1 num_results += 1
...@@ -184,7 +191,7 @@ class CsvWriter(object): ...@@ -184,7 +191,7 @@ class CsvWriter(object):
None None
""" """
submission = sub_api.get_submission_and_student(submission_uuid) submission = sub_api.get_submission_and_student(submission_uuid, read_replica=True)
self._write_unicode('submission', [ self._write_unicode('submission', [
submission['uuid'], submission['uuid'],
submission['student_item']['student_id'], submission['student_item']['student_id'],
...@@ -194,7 +201,7 @@ class CsvWriter(object): ...@@ -194,7 +201,7 @@ class CsvWriter(object):
json.dumps(submission['answer']) json.dumps(submission['answer'])
]) ])
score = sub_api.get_latest_score_for_submission(submission_uuid) score = sub_api.get_latest_score_for_submission(submission_uuid, read_replica=True)
if score is not None: if score is not None:
self._write_unicode('score', [ self._write_unicode('score', [
score['submission_uuid'], score['submission_uuid'],
...@@ -307,3 +314,20 @@ class CsvWriter(object): ...@@ -307,3 +314,20 @@ class CsvWriter(object):
if writer is not None: if writer is not None:
encoded_row = [unicode(field).encode('utf-8') for field in row] encoded_row = [unicode(field).encode('utf-8') for field in row]
writer.writerow(encoded_row) writer.writerow(encoded_row)
def _use_read_replica(self, queryset):
"""
Use the read replica if it's available.
Args:
queryset (QuerySet)
Returns:
QuerySet
"""
return (
queryset.using("read_replica")
if "read_replica" in settings.DATABASES
else queryset
)
\ No newline at end of file
...@@ -2,28 +2,40 @@ ...@@ -2,28 +2,40 @@
Test utilities Test utilities
""" """
from django.core.cache import cache from django.core.cache import cache
from django.test import TestCase from django.test import TestCase, TransactionTestCase
from openassessment.assessment.models.ai import ( from openassessment.assessment.models.ai import (
CLASSIFIERS_CACHE_IN_MEM, CLASSIFIERS_CACHE_IN_FILE CLASSIFIERS_CACHE_IN_MEM, CLASSIFIERS_CACHE_IN_FILE
) )
def _clear_all_caches():
"""Clear the default cache and any custom caches."""
cache.clear()
CLASSIFIERS_CACHE_IN_MEM.clear()
CLASSIFIERS_CACHE_IN_FILE.clear()
class CacheResetTest(TestCase): class CacheResetTest(TestCase):
""" """
Test case that resets the cache before and after each test. Test case that resets the cache before and after each test.
""" """
def setUp(self): def setUp(self):
super(CacheResetTest, self).setUp() super(CacheResetTest, self).setUp()
self._clear_all_caches() _clear_all_caches()
def tearDown(self): def tearDown(self):
super(CacheResetTest, self).tearDown() super(CacheResetTest, self).tearDown()
self._clear_all_caches() _clear_all_caches()
def _clear_all_caches(self):
""" class TransactionCacheResetTest(TransactionTestCase):
Clear the default cache and any custom caches. """
""" Transaction test case that resets the cache.
cache.clear() """
CLASSIFIERS_CACHE_IN_MEM.clear() def setUp(self):
CLASSIFIERS_CACHE_IN_FILE.clear() super(TransactionCacheResetTest, self).setUp()
_clear_all_caches()
def tearDown(self):
super(TransactionCacheResetTest, self).tearDown()
_clear_all_caches()
...@@ -8,14 +8,14 @@ from StringIO import StringIO ...@@ -8,14 +8,14 @@ from StringIO import StringIO
import csv import csv
from django.core.management import call_command from django.core.management import call_command
import ddt import ddt
from openassessment.test_utils import CacheResetTest
from submissions import api as sub_api from submissions import api as sub_api
from openassessment.test_utils import TransactionCacheResetTest
from openassessment.workflow import api as workflow_api from openassessment.workflow import api as workflow_api
from openassessment.data import CsvWriter from openassessment.data import CsvWriter
@ddt.ddt @ddt.ddt
class CsvWriterTest(CacheResetTest): class CsvWriterTest(TransactionCacheResetTest):
""" """
Test for writing openassessment data to CSV. Test for writing openassessment data to CSV.
""" """
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
git+https://github.com/edx/XBlock.git@fc5fea25c973ec66d8db63cf69a817ce624f5ef5#egg=XBlock git+https://github.com/edx/XBlock.git@fc5fea25c973ec66d8db63cf69a817ce624f5ef5#egg=XBlock
git+https://github.com/edx/xblock-sdk.git@643900aadcb18aaeb7fe67271ca9dbf36e463ee6#egg=xblock-sdk git+https://github.com/edx/xblock-sdk.git@643900aadcb18aaeb7fe67271ca9dbf36e463ee6#egg=xblock-sdk
edx-submissions==0.0.2 edx-submissions==0.0.3
# Third Party Requirements # Third Party Requirements
boto==2.13.3 boto==2.13.3
......
...@@ -23,12 +23,13 @@ NOSE_ARGS = [ ...@@ -23,12 +23,13 @@ NOSE_ARGS = [
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.sqlite3', 'ENGINE': 'django.db.backends.sqlite3',
'NAME': '', 'NAME': 'test_ora2db',
'USER': '', 'TEST_NAME': 'test_ora2db',
'PASSWORD': '', },
'HOST': '', 'read_replica': {
'PORT': '', 'ENGINE': 'django.db.backends.sqlite3',
} 'TEST_MIRROR': 'default',
},
} }
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment