Merge pull request #467 from edx/fix/adam/file-upload
Fix/adam/file upload
This commit is contained in:
@@ -1,16 +1,18 @@
|
||||
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
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
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']
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user