Commit 420b0920 by Adam

Merge pull request #467 from edx/fix/adam/file-upload

Fix/adam/file upload
parents 86ee2bca 2b404622
import json
from datetime import datetime
from pytz import UTC
from django.http import HttpResponse
from xmodule.modulestore.django import modulestore
from dogapi import dog_stats_api
@dog_stats_api.timed('edxapp.heartbeat')
def heartbeat(request):
"""
Simple view that a loadbalancer can check to verify that the app is up
"""
output = {
'date': datetime.now().isoformat(),
'date': datetime.now(UTC).isoformat(),
'courses': [course.location.url() for course in modulestore().get_courses()],
}
return HttpResponse(json.dumps(output, indent=4))
......@@ -69,30 +69,33 @@ class UserProfile(models.Model):
location = models.CharField(blank=True, max_length=255, db_index=True)
# Optional demographic data we started capturing from Fall 2012
this_year = datetime.now().year
this_year = datetime.now(UTC).year
VALID_YEARS = range(this_year, this_year - 120, -1)
year_of_birth = models.IntegerField(blank=True, null=True, db_index=True)
GENDER_CHOICES = (('m', 'Male'), ('f', 'Female'), ('o', 'Other'))
gender = models.CharField(blank=True, null=True, max_length=6, db_index=True,
choices=GENDER_CHOICES)
gender = models.CharField(
blank=True, null=True, max_length=6, db_index=True, choices=GENDER_CHOICES
)
# [03/21/2013] removed these, but leaving comment since there'll still be
# p_se and p_oth in the existing data in db.
# ('p_se', 'Doctorate in science or engineering'),
# ('p_oth', 'Doctorate in another field'),
LEVEL_OF_EDUCATION_CHOICES = (('p', 'Doctorate'),
('m', "Master's or professional degree"),
('b', "Bachelor's degree"),
('a', "Associate's degree"),
('hs', "Secondary/high school"),
('jhs', "Junior secondary/junior high/middle school"),
('el', "Elementary/primary school"),
('none', "None"),
('other', "Other"))
LEVEL_OF_EDUCATION_CHOICES = (
('p', 'Doctorate'),
('m', "Master's or professional degree"),
('b', "Bachelor's degree"),
('a', "Associate's degree"),
('hs', "Secondary/high school"),
('jhs', "Junior secondary/junior high/middle school"),
('el', "Elementary/primary school"),
('none', "None"),
('other', "Other")
)
level_of_education = models.CharField(
blank=True, null=True, max_length=6, db_index=True,
choices=LEVEL_OF_EDUCATION_CHOICES
)
blank=True, null=True, max_length=6, db_index=True,
choices=LEVEL_OF_EDUCATION_CHOICES
)
mailing_address = models.TextField(blank=True, null=True)
goals = models.TextField(blank=True, null=True)
allow_certificate = models.BooleanField(default=1)
......@@ -307,18 +310,18 @@ class TestCenterUserForm(ModelForm):
ACCOMMODATION_REJECTED_CODE = 'NONE'
ACCOMMODATION_CODES = (
(ACCOMMODATION_REJECTED_CODE, 'No Accommodation Granted'),
('EQPMNT', 'Equipment'),
('ET12ET', 'Extra Time - 1/2 Exam Time'),
('ET30MN', 'Extra Time - 30 Minutes'),
('ETDBTM', 'Extra Time - Double Time'),
('SEPRMM', 'Separate Room'),
('SRREAD', 'Separate Room and Reader'),
('SRRERC', 'Separate Room and Reader/Recorder'),
('SRRECR', 'Separate Room and Recorder'),
('SRSEAN', 'Separate Room and Service Animal'),
('SRSGNR', 'Separate Room and Sign Language Interpreter'),
)
(ACCOMMODATION_REJECTED_CODE, 'No Accommodation Granted'),
('EQPMNT', 'Equipment'),
('ET12ET', 'Extra Time - 1/2 Exam Time'),
('ET30MN', 'Extra Time - 30 Minutes'),
('ETDBTM', 'Extra Time - Double Time'),
('SEPRMM', 'Separate Room'),
('SRREAD', 'Separate Room and Reader'),
('SRRERC', 'Separate Room and Reader/Recorder'),
('SRRECR', 'Separate Room and Recorder'),
('SRSEAN', 'Separate Room and Service Animal'),
('SRSGNR', 'Separate Room and Sign Language Interpreter'),
)
ACCOMMODATION_CODE_DICT = {code: name for (code, name) in ACCOMMODATION_CODES}
......@@ -572,7 +575,6 @@ class TestCenterRegistrationForm(ModelForm):
return code
def get_testcenter_registration(user, course_id, exam_series_code):
try:
tcu = TestCenterUser.objects.get(user=user)
......
......@@ -111,9 +111,9 @@ def get_date_for_press(publish_date):
# strip off extra months, and just use the first:
date = re.sub(multimonth_pattern, ", ", publish_date)
if re.search(day_pattern, date):
date = datetime.datetime.strptime(date, "%B %d, %Y")
date = datetime.datetime.strptime(date, "%B %d, %Y").replace(tzinfo=UTC)
else:
date = datetime.datetime.strptime(date, "%B, %Y")
date = datetime.datetime.strptime(date, "%B, %Y").replace(tzinfo=UTC)
return date
......@@ -1100,7 +1100,7 @@ def confirm_email_change(request, key):
meta = up.get_meta()
if 'old_emails' not in meta:
meta['old_emails'] = []
meta['old_emails'].append([user.email, datetime.datetime.now().isoformat()])
meta['old_emails'].append([user.email, datetime.datetime.now(UTC).isoformat()])
up.set_meta(meta)
up.save()
# Send it to the old email...
......@@ -1198,7 +1198,7 @@ def accept_name_change_by_id(id):
meta = up.get_meta()
if 'old_names' not in meta:
meta['old_names'] = []
meta['old_names'].append([up.name, pnc.rationale, datetime.datetime.now().isoformat()])
meta['old_names'].append([up.name, pnc.rationale, datetime.datetime.now(UTC).isoformat()])
up.set_meta(meta)
up.name = pnc.new_name
......
......@@ -32,6 +32,8 @@ import capa.xqueue_interface as xqueue_interface
import capa.responsetypes as responsetypes
from capa.safe_exec import safe_exec
from pytz import UTC
# dict of tagname, Response Class -- this should come from auto-registering
response_tag_dict = dict([(x.response_tag, x) for x in responsetypes.__all__])
......@@ -42,13 +44,22 @@ solution_tags = ['solution']
response_properties = ["codeparam", "responseparam", "answer", "openendedparam"]
# special problem tags which should be turned into innocuous HTML
html_transforms = {'problem': {'tag': 'div'},
'text': {'tag': 'span'},
'math': {'tag': 'span'},
}
html_transforms = {
'problem': {'tag': 'div'},
'text': {'tag': 'span'},
'math': {'tag': 'span'},
}
# These should be removed from HTML output, including all subelements
html_problem_semantics = ["codeparam", "responseparam", "answer", "script", "hintgroup", "openendedparam", "openendedrubric"]
html_problem_semantics = [
"codeparam",
"responseparam",
"answer",
"script",
"hintgroup",
"openendedparam",
"openendedrubric"
]
log = logging.getLogger(__name__)
......@@ -242,11 +253,15 @@ class LoncapaProblem(object):
return None
# Get a list of timestamps of all queueing requests, then convert it to a DateTime object
queuetime_strs = [self.correct_map.get_queuetime_str(answer_id)
for answer_id in self.correct_map
if self.correct_map.is_queued(answer_id)]
queuetimes = [datetime.strptime(qt_str, xqueue_interface.dateformat)
for qt_str in queuetime_strs]
queuetime_strs = [
self.correct_map.get_queuetime_str(answer_id)
for answer_id in self.correct_map
if self.correct_map.is_queued(answer_id)
]
queuetimes = [
datetime.strptime(qt_str, xqueue_interface.dateformat).replace(tzinfo=UTC)
for qt_str in queuetime_strs
]
return max(queuetimes)
......@@ -404,10 +419,16 @@ class LoncapaProblem(object):
# open using ModuleSystem OSFS filestore
ifp = self.system.filestore.open(filename)
except Exception as err:
log.warning('Error %s in problem xml include: %s' % (
err, etree.tostring(inc, pretty_print=True)))
log.warning('Cannot find file %s in %s' % (
filename, self.system.filestore))
log.warning(
'Error %s in problem xml include: %s' % (
err, etree.tostring(inc, pretty_print=True)
)
)
log.warning(
'Cannot find file %s in %s' % (
filename, self.system.filestore
)
)
# if debugging, don't fail - just log error
# TODO (vshnayder): need real error handling, display to users
if not self.system.get('DEBUG'):
......@@ -418,8 +439,11 @@ class LoncapaProblem(object):
# read in and convert to XML
incxml = etree.XML(ifp.read())
except Exception as err:
log.warning('Error %s in problem xml include: %s' % (
err, etree.tostring(inc, pretty_print=True)))
log.warning(
'Error %s in problem xml include: %s' % (
err, etree.tostring(inc, pretty_print=True)
)
)
log.warning('Cannot parse XML in %s' % (filename))
# if debugging, don't fail - just log error
# TODO (vshnayder): same as above
......@@ -579,8 +603,9 @@ class LoncapaProblem(object):
# let each Response render itself
if problemtree in self.responders:
overall_msg = self.correct_map.get_overall_message()
return self.responders[problemtree].render_html(self._extract_html,
response_msg=overall_msg)
return self.responders[problemtree].render_html(
self._extract_html, response_msg=overall_msg
)
# let each custom renderer render itself:
if problemtree.tag in customrender.registry.registered_tags():
......@@ -628,9 +653,10 @@ class LoncapaProblem(object):
answer_id = 1
input_tags = inputtypes.registry.registered_tags()
inputfields = tree.xpath("|".join(['//' + response.tag + '[@id=$id]//' + x
for x in (input_tags + solution_tags)]),
id=response_id_str)
inputfields = tree.xpath(
"|".join(['//' + response.tag + '[@id=$id]//' + x for x in (input_tags + solution_tags)]),
id=response_id_str
)
# assign one answer_id for each input type or solution type
for entry in inputfields:
......
......@@ -37,23 +37,27 @@ class CorrectMap(object):
return self.cmap.__iter__()
# See the documentation for 'set_dict' for the use of kwargs
def set(self,
answer_id=None,
correctness=None,
npoints=None,
msg='',
hint='',
hintmode=None,
queuestate=None, **kwargs):
def set(
self,
answer_id=None,
correctness=None,
npoints=None,
msg='',
hint='',
hintmode=None,
queuestate=None,
**kwargs
):
if answer_id is not None:
self.cmap[str(answer_id)] = {'correctness': correctness,
'npoints': npoints,
'msg': msg,
'hint': hint,
'hintmode': hintmode,
'queuestate': queuestate,
}
self.cmap[str(answer_id)] = {
'correctness': correctness,
'npoints': npoints,
'msg': msg,
'hint': hint,
'hintmode': hintmode,
'queuestate': queuestate,
}
def __repr__(self):
return repr(self.cmap)
......
......@@ -33,6 +33,7 @@ from shapely.geometry import Point, MultiPoint
from calc import evaluator, UndefinedVariable
from . import correctmap
from datetime import datetime
from pytz import UTC
from .util import *
from lxml import etree
from lxml.html.soupparser import fromstring as fromstring_bs # uses Beautiful Soup!!! FIXME?
......@@ -1365,9 +1366,11 @@ class CodeResponse(LoncapaResponse):
# Note that submission can be a file
submission = student_answers[self.answer_id]
except Exception as err:
log.error('Error in CodeResponse %s: cannot get student answer for %s;'
' student_answers=%s' %
(err, self.answer_id, convert_files_to_filenames(student_answers)))
log.error(
'Error in CodeResponse %s: cannot get student answer for %s;'
' student_answers=%s' %
(err, self.answer_id, convert_files_to_filenames(student_answers))
)
raise Exception(err)
# We do not support xqueue within Studio.
......@@ -1381,19 +1384,20 @@ class CodeResponse(LoncapaResponse):
#------------------------------------------------------------
qinterface = self.system.xqueue['interface']
qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat)
qtime = datetime.strftime(datetime.now(UTC), xqueue_interface.dateformat)
anonymous_student_id = self.system.anonymous_student_id
# Generate header
queuekey = xqueue_interface.make_hashkey(str(self.system.seed) + qtime +
anonymous_student_id +
self.answer_id)
queuekey = xqueue_interface.make_hashkey(
str(self.system.seed) + qtime + anonymous_student_id + self.answer_id
)
callback_url = self.system.xqueue['construct_callback']()
xheader = xqueue_interface.make_xheader(
lms_callback_url=callback_url,
lms_key=queuekey,
queue_name=self.queue_name)
queue_name=self.queue_name
)
# Generate body
if is_list_of_files(submission):
......@@ -1406,9 +1410,10 @@ class CodeResponse(LoncapaResponse):
# Metadata related to the student submission revealed to the external
# grader
student_info = {'anonymous_student_id': anonymous_student_id,
'submission_time': qtime,
}
student_info = {
'anonymous_student_id': anonymous_student_id,
'submission_time': qtime,
}
contents.update({'student_info': json.dumps(student_info)})
# Submit request. When successful, 'msg' is the prior length of the
......
......@@ -18,6 +18,8 @@ from capa.correctmap import CorrectMap
from capa.util import convert_files_to_filenames
from capa.xqueue_interface import dateformat
from pytz import UTC
class ResponseTest(unittest.TestCase):
""" Base class for tests of capa responses."""
......@@ -333,8 +335,9 @@ class SymbolicResponseTest(ResponseTest):
correct_map = problem.grade_answers(input_dict)
self.assertEqual(correct_map.get_correctness('1_2_1'),
expected_correctness)
self.assertEqual(
correct_map.get_correctness('1_2_1'), expected_correctness
)
class OptionResponseTest(ResponseTest):
......@@ -702,7 +705,7 @@ class CodeResponseTest(ResponseTest):
# Now we queue the LCP
cmap = CorrectMap()
for i, answer_id in enumerate(answer_ids):
queuestate = CodeResponseTest.make_queuestate(i, datetime.now())
queuestate = CodeResponseTest.make_queuestate(i, datetime.now(UTC))
cmap.update(CorrectMap(answer_id=answer_ids[i], queuestate=queuestate))
self.problem.correct_map.update(cmap)
......@@ -718,7 +721,7 @@ class CodeResponseTest(ResponseTest):
old_cmap = CorrectMap()
for i, answer_id in enumerate(answer_ids):
queuekey = 1000 + i
queuestate = CodeResponseTest.make_queuestate(queuekey, datetime.now())
queuestate = CodeResponseTest.make_queuestate(queuekey, datetime.now(UTC))
old_cmap.update(CorrectMap(answer_id=answer_ids[i], queuestate=queuestate))
# Message format common to external graders
......@@ -778,13 +781,15 @@ class CodeResponseTest(ResponseTest):
cmap = CorrectMap()
for i, answer_id in enumerate(answer_ids):
queuekey = 1000 + i
latest_timestamp = datetime.now()
latest_timestamp = datetime.now(UTC)
queuestate = CodeResponseTest.make_queuestate(queuekey, latest_timestamp)
cmap.update(CorrectMap(answer_id=answer_id, queuestate=queuestate))
self.problem.correct_map.update(cmap)
# Queue state only tracks up to second
latest_timestamp = datetime.strptime(datetime.strftime(latest_timestamp, dateformat), dateformat)
latest_timestamp = datetime.strptime(
datetime.strftime(latest_timestamp, dateformat), dateformat
).replace(tzinfo=UTC)
self.assertEquals(self.problem.get_recentmost_queuetime(), latest_timestamp)
......
......@@ -30,9 +30,11 @@ def make_xheader(lms_callback_url, lms_key, queue_name):
'queue_name': designate a specific queue within xqueue server, e.g. 'MITx-6.00x' (string)
}
"""
return json.dumps({'lms_callback_url': lms_callback_url,
'lms_key': lms_key,
'queue_name': queue_name})
return json.dumps({
'lms_callback_url': lms_callback_url,
'lms_key': lms_key,
'queue_name': queue_name
})
def parse_xreply(xreply):
......@@ -60,7 +62,7 @@ class XQueueInterface(object):
'''
def __init__(self, url, django_auth, requests_auth=None):
self.url = url
self.url = url
self.auth = django_auth
self.session = requests.session(auth=requests_auth)
......@@ -95,13 +97,13 @@ class XQueueInterface(object):
return (error, msg)
def _login(self):
payload = {'username': self.auth['username'],
'password': self.auth['password']}
payload = {
'username': self.auth['username'],
'password': self.auth['password']
}
return self._http_post(self.url + '/xqueue/login/', payload)
def _send_to_queue(self, header, body, files_to_upload):
payload = {'xqueue_header': header,
'xqueue_body': body}
......@@ -112,7 +114,6 @@ class XQueueInterface(object):
return self._http_post(self.url + '/xqueue/submit/', payload, files=files)
def _http_post(self, url, data, files=None):
try:
r = self.session.post(url, data=data, files=files)
......
......@@ -1126,8 +1126,12 @@ class CapaDescriptor(CapaFields, RawDescriptor):
mako_template = "widgets/problem-edit.html"
js = {'coffee': [resource_string(__name__, 'js/src/problem/edit.coffee')]}
js_module_name = "MarkdownEditingDescriptor"
css = {'scss': [resource_string(__name__, 'css/editor/edit.scss'),
resource_string(__name__, 'css/problem/edit.scss')]}
css = {
'scss': [
resource_string(__name__, 'css/editor/edit.scss'),
resource_string(__name__, 'css/problem/edit.scss')
]
}
# Capa modules have some additional metadata:
# TODO (vshnayder): do problems have any other metadata? Do they
......
......@@ -80,6 +80,7 @@ class Date(ModelType):
TIMEDELTA_REGEX = re.compile(r'^((?P<days>\d+?) day(?:s?))?(\s)?((?P<hours>\d+?) hour(?:s?))?(\s)?((?P<minutes>\d+?) minute(?:s)?)?(\s)?((?P<seconds>\d+?) second(?:s)?)?$')
class Timedelta(ModelType):
def from_json(self, time_str):
"""
......
......@@ -19,6 +19,7 @@ import openendedchild
from numpy import median
from datetime import datetime
from pytz import UTC
from .combined_open_ended_rubric import CombinedOpenEndedRubric
......@@ -170,7 +171,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
if xqueue is None:
return {'success': False, 'msg': "Couldn't submit feedback."}
qinterface = xqueue['interface']
qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat)
qtime = datetime.strftime(datetime.now(UTC), xqueue_interface.dateformat)
anonymous_student_id = system.anonymous_student_id
queuekey = xqueue_interface.make_hashkey(str(system.seed) + qtime +
anonymous_student_id +
......@@ -224,7 +225,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
if xqueue is None:
return False
qinterface = xqueue['interface']
qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat)
qtime = datetime.strftime(datetime.now(UTC), xqueue_interface.dateformat)
anonymous_student_id = system.anonymous_student_id
......
......@@ -5,6 +5,7 @@ import re
import open_ended_image_submission
from xmodule.progress import Progress
import capa.xqueue_interface as xqueue_interface
from capa.util import *
from .peer_grading_service import PeerGradingService, MockPeerGradingService
import controller_query_service
......@@ -334,12 +335,15 @@ class OpenEndedChild(object):
log.exception("Could not create image and check it.")
if image_ok:
image_key = image_data.name + datetime.now().strftime("%Y%m%d%H%M%S")
image_key = image_data.name + datetime.now(UTC).strftime(
xqueue_interface.dateformat
)
try:
image_data.seek(0)
success, s3_public_url = open_ended_image_submission.upload_to_s3(image_data, image_key,
self.s3_interface)
success, s3_public_url = open_ended_image_submission.upload_to_s3(
image_data, image_key, self.s3_interface
)
except:
log.exception("Could not upload image to S3.")
......
......@@ -14,6 +14,7 @@ from xmodule.modulestore import Location
from lxml import etree
import capa.xqueue_interface as xqueue_interface
from datetime import datetime
from pytz import UTC
import logging
log = logging.getLogger(__name__)
......@@ -212,7 +213,7 @@ class OpenEndedModuleTest(unittest.TestCase):
'submission_id': '1',
'grader_id': '1',
'score': 3}
qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat)
qtime = datetime.strftime(datetime.now(UTC), xqueue_interface.dateformat)
student_info = {'anonymous_student_id': self.test_system.anonymous_student_id,
'submission_time': qtime}
contents = {
......@@ -233,7 +234,7 @@ class OpenEndedModuleTest(unittest.TestCase):
def test_send_to_grader(self):
submission = "This is a student submission"
qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat)
qtime = datetime.strftime(datetime.now(UTC), xqueue_interface.dateformat)
student_info = {'anonymous_student_id': self.test_system.anonymous_student_id,
'submission_time': qtime}
contents = self.openendedmodule.payload.copy()
......@@ -632,6 +633,7 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore):
module.handle_ajax("reset", {})
self.assertEqual(module.state, "initial")
class OpenEndedModuleXmlAttemptTest(unittest.TestCase, DummyModulestore):
"""
Test if student is able to reset the problem
......
import os, polib
import os
import polib
from unittest import TestCase
from nose.plugins.skip import SkipTest
from datetime import datetime, timedelta
from pytz import UTC
import extract
from config import CONFIGURATION
......@@ -9,6 +11,7 @@ from config import CONFIGURATION
# Make sure setup runs only once
SETUP_HAS_RUN = False
class TestExtract(TestCase):
"""
Tests functionality of i18n/extract.py
......@@ -19,20 +22,20 @@ class TestExtract(TestCase):
# Skip this test because it takes too long (>1 minute)
# TODO: figure out how to declare a "long-running" test suite
# and add this test to it.
raise SkipTest()
raise SkipTest()
global SETUP_HAS_RUN
# Subtract 1 second to help comparisons with file-modify time succeed,
# since os.path.getmtime() is not millisecond-accurate
self.start_time = datetime.now() - timedelta(seconds=1)
self.start_time = datetime.now(UTC) - timedelta(seconds=1)
super(TestExtract, self).setUp()
if not SETUP_HAS_RUN:
# Run extraction script. Warning, this takes 1 minute or more
extract.main()
SETUP_HAS_RUN = True
def get_files (self):
def get_files(self):
"""
This is a generator.
Returns the fully expanded filenames for all extracted files
......@@ -65,19 +68,21 @@ class TestExtract(TestCase):
entry2.msgid = "This is not a keystring"
self.assertTrue(extract.is_key_string(entry1.msgid))
self.assertFalse(extract.is_key_string(entry2.msgid))
def test_headers(self):
"""Verify all headers have been modified"""
for path in self.get_files():
po = polib.pofile(path)
header = po.header
self.assertEqual(header.find('edX translation file'), 0,
msg='Missing header in %s:\n"%s"' % \
(os.path.basename(path), header))
self.assertEqual(
header.find('edX translation file'),
0,
msg='Missing header in %s:\n"%s"' % (os.path.basename(path), header)
)
def test_metadata(self):
"""Verify all metadata has been modified"""
for path in self.get_files():
for path in self.get_files():
po = polib.pofile(path)
metadata = po.metadata
value = metadata['Report-Msgid-Bugs-To']
......
import os, string, random, re
import os
import string
import random
import re
from polib import pofile
from unittest import TestCase
from datetime import datetime, timedelta
from pytz import UTC
import generate
from config import CONFIGURATION
class TestGenerate(TestCase):
"""
Tests functionality of i18n/generate.py
......@@ -15,7 +20,7 @@ class TestGenerate(TestCase):
def setUp(self):
# Subtract 1 second to help comparisons with file-modify time succeed,
# since os.path.getmtime() is not millisecond-accurate
self.start_time = datetime.now() - timedelta(seconds=1)
self.start_time = datetime.now(UTC) - timedelta(seconds=1)
def test_merge(self):
"""
......@@ -49,7 +54,7 @@ class TestGenerate(TestCase):
"""
This is invoked by test_main to ensure that it runs after
calling generate.main().
There should be exactly three merge comment headers
in our merged .po file. This counts them to be sure.
A merge comment looks like this:
......
......@@ -8,6 +8,7 @@ from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.django import modulestore
from certificates.models import CertificateStatuses
import datetime
from pytz import UTC
class Command(BaseCommand):
......@@ -41,7 +42,6 @@ class Command(BaseCommand):
'whose entry in the certificate table matches STATUS. '
'STATUS can be generating, unavailable, deleted, error '
'or notpassing.'),
)
def handle(self, *args, **options):
......@@ -83,20 +83,20 @@ class Command(BaseCommand):
xq = XQueueCertInterface()
total = enrolled_students.count()
count = 0
start = datetime.datetime.now()
start = datetime.datetime.now(UTC)
for student in enrolled_students:
count += 1
if count % STATUS_INTERVAL == 0:
# Print a status update with an approximation of
# how much time is left based on how long the last
# interval took
diff = datetime.datetime.now() - start
diff = datetime.datetime.now(UTC) - start
timeleft = diff * (total - count) / STATUS_INTERVAL
hours, remainder = divmod(timeleft.seconds, 3600)
minutes, seconds = divmod(remainder, 60)
print "{0}/{1} completed ~{2:02}:{3:02}m remaining".format(
count, total, hours, minutes)
start = datetime.datetime.now()
start = datetime.datetime.now(UTC)
if certificate_status_for_student(
student, course_id)['status'] in valid_statuses:
......
......@@ -213,22 +213,28 @@ def get_module_for_descriptor_internal(user, descriptor, model_data_cache, cours
return None
# Setup system context for module instance
ajax_url = reverse('modx_dispatch',
kwargs=dict(course_id=course_id,
location=descriptor.location.url(),
dispatch=''),
)
ajax_url = reverse(
'modx_dispatch',
kwargs=dict(
course_id=course_id,
location=descriptor.location.url(),
dispatch=''
),
)
# Intended use is as {ajax_url}/{dispatch_command}, so get rid of the trailing slash.
ajax_url = ajax_url.rstrip('/')
def make_xqueue_callback(dispatch='score_update'):
# Fully qualified callback URL for external queueing system
relative_xqueue_callback_url = reverse('xqueue_callback',
kwargs=dict(course_id=course_id,
userid=str(user.id),
mod_id=descriptor.location.url(),
dispatch=dispatch),
)
relative_xqueue_callback_url = reverse(
'xqueue_callback',
kwargs=dict(
course_id=course_id,
userid=str(user.id),
mod_id=descriptor.location.url(),
dispatch=dispatch
),
)
return xqueue_callback_url_prefix + relative_xqueue_callback_url
# Default queuename is course-specific and is derived from the course that
......@@ -313,10 +319,12 @@ def get_module_for_descriptor_internal(user, descriptor, model_data_cache, cours
score_bucket = get_score_bucket(student_module.grade, student_module.max_grade)
org, course_num, run = course_id.split("/")
tags = ["org:{0}".format(org),
"course:{0}".format(course_num),
"run:{0}".format(run),
"score_bucket:{0}".format(score_bucket)]
tags = [
"org:{0}".format(org),
"course:{0}".format(course_num),
"run:{0}".format(run),
"score_bucket:{0}".format(score_bucket)
]
if grade_bucket_type is not None:
tags.append('type:%s' % grade_bucket_type)
......@@ -326,38 +334,41 @@ def get_module_for_descriptor_internal(user, descriptor, model_data_cache, cours
# TODO (cpennington): When modules are shared between courses, the static
# prefix is going to have to be specific to the module, not the directory
# that the xml was loaded from
system = ModuleSystem(track_function=track_function,
render_template=render_to_string,
ajax_url=ajax_url,
xqueue=xqueue,
# TODO (cpennington): Figure out how to share info between systems
filestore=descriptor.system.resources_fs,
get_module=inner_get_module,
user=user,
# TODO (cpennington): This should be removed when all html from
# a module is coming through get_html and is therefore covered
# by the replace_static_urls code below
replace_urls=partial(
static_replace.replace_static_urls,
data_directory=getattr(descriptor, 'data_dir', None),
course_namespace=descriptor.location._replace(category=None, name=None),
),
node_path=settings.NODE_PATH,
xblock_model_data=xblock_model_data,
publish=publish,
anonymous_student_id=unique_id_for_user(user),
course_id=course_id,
open_ended_grading_interface=open_ended_grading_interface,
s3_interface=s3_interface,
cache=cache,
can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)),
)
system = ModuleSystem(
track_function=track_function,
render_template=render_to_string,
ajax_url=ajax_url,
xqueue=xqueue,
# TODO (cpennington): Figure out how to share info between systems
filestore=descriptor.system.resources_fs,
get_module=inner_get_module,
user=user,
# TODO (cpennington): This should be removed when all html from
# a module is coming through get_html and is therefore covered
# by the replace_static_urls code below
replace_urls=partial(
static_replace.replace_static_urls,
data_directory=getattr(descriptor, 'data_dir', None),
course_namespace=descriptor.location._replace(category=None, name=None),
),
node_path=settings.NODE_PATH,
xblock_model_data=xblock_model_data,
publish=publish,
anonymous_student_id=unique_id_for_user(user),
course_id=course_id,
open_ended_grading_interface=open_ended_grading_interface,
s3_interface=s3_interface,
cache=cache,
can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)),
)
# pass position specified in URL to module through ModuleSystem
system.set('position', position)
system.set('DEBUG', settings.DEBUG)
if settings.MITX_FEATURES.get('ENABLE_PSYCHOMETRICS'):
system.set('psychometrics_handler', # set callback for updating PsychometricsData
make_psychometrics_data_update_handler(course_id, user, descriptor.location.url()))
system.set(
'psychometrics_handler', # set callback for updating PsychometricsData
make_psychometrics_data_update_handler(course_id, user, descriptor.location.url())
)
try:
module = descriptor.xmodule(system)
......@@ -381,13 +392,14 @@ def get_module_for_descriptor_internal(user, descriptor, model_data_cache, cours
system.set('user_is_staff', has_access(user, descriptor.location, 'staff', course_id))
_get_html = module.get_html
if wrap_xmodule_display == True:
if wrap_xmodule_display is True:
_get_html = wrap_xmodule(module.get_html, module, 'xmodule_display.html')
module.get_html = replace_static_urls(
_get_html,
getattr(descriptor, 'data_dir', None),
course_namespace=module.location._replace(category=None, name=None))
course_namespace=module.location._replace(category=None, name=None)
)
# Allow URLs of the form '/course/' refer to the root of multicourse directory
# hierarchy of this course
......
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