From af522af2ca386173a9b015a113444f554e0fe75c Mon Sep 17 00:00:00 2001 From: Adam Palay Date: Mon, 22 Jul 2013 11:06:58 -0400 Subject: [PATCH 1/4] formatting --- common/lib/capa/capa/capa_problem.py | 14 ++- common/lib/capa/capa/correctmap.py | 34 ++++--- common/lib/capa/capa/responsetypes.py | 24 +++-- common/lib/capa/capa/xqueue_interface.py | 19 ++-- common/lib/xmodule/xmodule/capa_module.py | 8 +- lms/djangoapps/courseware/module_render.py | 102 ++++++++++++--------- 6 files changed, 115 insertions(+), 86 deletions(-) diff --git a/common/lib/capa/capa/capa_problem.py b/common/lib/capa/capa/capa_problem.py index 2c813f49d5..c4dbc56d63 100644 --- a/common/lib/capa/capa/capa_problem.py +++ b/common/lib/capa/capa/capa_problem.py @@ -242,11 +242,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) + for qt_str in queuetime_strs + ] return max(queuetimes) diff --git a/common/lib/capa/capa/correctmap.py b/common/lib/capa/capa/correctmap.py index 950cd199fc..e50be92152 100644 --- a/common/lib/capa/capa/correctmap.py +++ b/common/lib/capa/capa/correctmap.py @@ -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) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index f99518c8ce..7adf337fe9 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1365,9 +1365,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. @@ -1386,14 +1388,15 @@ class CodeResponse(LoncapaResponse): 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 +1409,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 diff --git a/common/lib/capa/capa/xqueue_interface.py b/common/lib/capa/capa/xqueue_interface.py index 5cf2488af0..4da8e11d53 100644 --- a/common/lib/capa/capa/xqueue_interface.py +++ b/common/lib/capa/capa/xqueue_interface.py @@ -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) diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index e8f8baf45f..aa86cb6c5c 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -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 diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index de709f7652..56f1d206cd 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -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 From b744aaa3609c1d98abedfdcdaadc082e6eb6485b Mon Sep 17 00:00:00 2001 From: Adam Palay Date: Mon, 22 Jul 2013 16:12:07 -0400 Subject: [PATCH 2/4] make sure parsed times are set to UTC --- common/djangoapps/student/views.py | 4 +- common/lib/capa/capa/capa_problem.py | 56 +++++++++++++++++++--------- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 398a3f6efc..433578f3e9 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -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 diff --git a/common/lib/capa/capa/capa_problem.py b/common/lib/capa/capa/capa_problem.py index c4dbc56d63..c2bdeadc21 100644 --- a/common/lib/capa/capa/capa_problem.py +++ b/common/lib/capa/capa/capa_problem.py @@ -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__) @@ -248,7 +259,7 @@ class LoncapaProblem(object): if self.correct_map.is_queued(answer_id) ] queuetimes = [ - datetime.strptime(qt_str, xqueue_interface.dateformat) + datetime.strptime(qt_str, xqueue_interface.dateformat).replace(tzinfo=UTC) for qt_str in queuetime_strs ] @@ -408,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'): @@ -422,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 @@ -583,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(): @@ -632,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: From b2b3a50400662bd8867ad78edf97e440b0289c93 Mon Sep 17 00:00:00 2001 From: Adam Palay Date: Mon, 22 Jul 2013 16:21:47 -0400 Subject: [PATCH 3/4] convert datetime.now() to datetime.now(UTC) for xqueue --- common/lib/capa/capa/responsetypes.py | 3 ++- common/lib/capa/capa/tests/test_responsetypes.py | 13 +++++++++---- common/lib/xmodule/xmodule/fields.py | 1 + .../open_ended_grading_classes/open_ended_module.py | 5 +++-- .../xmodule/tests/test_combined_open_ended.py | 6 ++++-- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 7adf337fe9..03c82ea218 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -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? @@ -1383,7 +1384,7 @@ 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 diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index 2a15145579..5679fcef93 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -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): @@ -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) diff --git a/common/lib/xmodule/xmodule/fields.py b/common/lib/xmodule/xmodule/fields.py index 465993a51f..dc2f000286 100644 --- a/common/lib/xmodule/xmodule/fields.py +++ b/common/lib/xmodule/xmodule/fields.py @@ -80,6 +80,7 @@ class Date(ModelType): TIMEDELTA_REGEX = re.compile(r'^((?P\d+?) day(?:s?))?(\s)?((?P\d+?) hour(?:s?))?(\s)?((?P\d+?) minute(?:s)?)?(\s)?((?P\d+?) second(?:s)?)?$') + class Timedelta(ModelType): def from_json(self, time_str): """ diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py index 8d8a85f788..2e7a3eaf89 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py @@ -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 diff --git a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py index 8162d588bb..4fd0ddccf7 100644 --- a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py +++ b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py @@ -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 From 2b404622639d1b03db92c86340df7f7cebc9824e Mon Sep 17 00:00:00 2001 From: Adam Palay Date: Mon, 22 Jul 2013 16:36:30 -0400 Subject: [PATCH 4/4] convert all datetime.now() to datetime.now(UTC) --- common/djangoapps/heartbeat/views.py | 4 +- common/djangoapps/student/models.py | 58 ++++++++++--------- common/djangoapps/student/views.py | 4 +- .../lib/capa/capa/tests/test_responsetypes.py | 4 +- .../openendedchild.py | 10 +++- i18n/tests/test_extract.py | 25 ++++---- i18n/tests/test_generate.py | 11 +++- .../management/commands/ungenerated_certs.py | 8 +-- 8 files changed, 71 insertions(+), 53 deletions(-) diff --git a/common/djangoapps/heartbeat/views.py b/common/djangoapps/heartbeat/views.py index d7c3a32192..0cee7116b4 100644 --- a/common/djangoapps/heartbeat/views.py +++ b/common/djangoapps/heartbeat/views.py @@ -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)) diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index af93c34317..e6530338a8 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -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) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 433578f3e9..553643bde7 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -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 diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index 5679fcef93..a756dc640e 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -705,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) @@ -721,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 diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py index 047ab0244c..10f939b270 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py @@ -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.") diff --git a/i18n/tests/test_extract.py b/i18n/tests/test_extract.py index 7e8b1a9d2b..3ef87a3736 100644 --- a/i18n/tests/test_extract.py +++ b/i18n/tests/test_extract.py @@ -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'] diff --git a/i18n/tests/test_generate.py b/i18n/tests/test_generate.py index 468858664f..b9a36ada33 100644 --- a/i18n/tests/test_generate.py +++ b/i18n/tests/test_generate.py @@ -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: diff --git a/lms/djangoapps/certificates/management/commands/ungenerated_certs.py b/lms/djangoapps/certificates/management/commands/ungenerated_certs.py index ab1459766a..c9f944158a 100644 --- a/lms/djangoapps/certificates/management/commands/ungenerated_certs.py +++ b/lms/djangoapps/certificates/management/commands/ungenerated_certs.py @@ -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: