From d7194e6bec3de27c35412e15da7e6c1cc3edc666 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Wed, 29 May 2013 10:19:16 -0400 Subject: [PATCH] struct_time to datetime conversion. --- .../contentstore/tests/test_contentstore.py | 6 +-- .../tests/test_course_settings.py | 10 +--- cms/djangoapps/contentstore/views/assets.py | 6 +-- cms/djangoapps/contentstore/views/course.py | 33 ++++++------ .../models/settings/course_details.py | 20 +++---- cms/templates/edit_subsection.html | 20 ++++--- cms/templates/overview.html | 9 ++-- common/djangoapps/contentserver/middleware.py | 7 +-- .../commands/pearson_make_tc_registration.py | 5 +- common/djangoapps/student/models.py | 17 +++--- common/djangoapps/xmodule_modifiers.py | 9 ++-- common/lib/capa/capa/inputtypes.py | 12 ++--- common/lib/xmodule/xmodule/capa_module.py | 8 +-- common/lib/xmodule/xmodule/course_module.py | 45 ++++++++-------- common/lib/xmodule/xmodule/fields.py | 24 ++++++--- common/lib/xmodule/xmodule/foldit_module.py | 9 ++-- .../lib/xmodule/xmodule/modulestore/draft.py | 3 +- common/lib/xmodule/xmodule/modulestore/xml.py | 6 +-- .../openendedchild.py | 35 +++++++------ .../xmodule/xmodule/peer_grading_module.py | 47 +++++++++-------- .../xmodule/tests/test_course_module.py | 11 ++-- .../xmodule/xmodule/tests/test_date_utils.py | 35 ------------- .../lib/xmodule/xmodule/tests/test_fields.py | 25 +++------ .../lib/xmodule/xmodule/tests/test_import.py | 29 ++++++++--- common/lib/xmodule/xmodule/timeinfo.py | 3 +- common/lib/xmodule/xmodule/timeparse.py | 11 ++-- common/lib/xmodule/xmodule/util/date_utils.py | 40 +++++--------- .../Administrivia_and_Circuit_Elements.xml | 52 +++++++++++-------- lms/djangoapps/courseware/access.py | 20 +++---- .../courseware/tests/test_access.py | 18 +++---- lms/djangoapps/courseware/tests/tests.py | 41 +++++++-------- lms/djangoapps/django_comment_client/utils.py | 16 ++---- 32 files changed, 293 insertions(+), 339 deletions(-) delete mode 100644 common/lib/xmodule/xmodule/tests/test_date_utils.py diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index 232b68ecc8..1e6ab8cd86 100644 --- a/cms/djangoapps/contentstore/tests/test_contentstore.py +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -271,7 +271,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ) self.assertTrue(getattr(draft_problem, 'is_draft', False)) - #now requery with depth + # now requery with depth course = modulestore('draft').get_item( Location(['i4x', 'edX', 'simple', 'course', '2012_Fall', None]), depth=None @@ -539,7 +539,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): on_disk = loads(grading_policy.read()) self.assertEqual(on_disk, course.grading_policy) - #check for policy.json + # check for policy.json self.assertTrue(filesystem.exists('policy.json')) # compare what's on disk to what we have in the course module @@ -990,7 +990,7 @@ class ContentStoreTest(ModuleStoreTestCase): def test_metadata_inheritance(self): module_store = modulestore('direct') - import_from_xml(module_store, 'common/test/data/', ['full']) + import_from_xml(module_store, 'common/test/data/', ['full'], verbose=True) course = module_store.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None])) diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index 2a4ff46038..c1b7a9fa0e 100644 --- a/cms/djangoapps/contentstore/tests/test_course_settings.py +++ b/cms/djangoapps/contentstore/tests/test_course_settings.py @@ -151,22 +151,16 @@ class CourseDetailsViewTest(CourseTestCase): self.assertEqual(details['intro_video'], encoded.get('intro_video', None), context + " intro_video not ==") self.assertEqual(details['effort'], encoded['effort'], context + " efforts not ==") - @staticmethod - def struct_to_datetime(struct_time): - return datetime.datetime(*struct_time[:6], tzinfo=UTC()) - def compare_date_fields(self, details, encoded, context, field): if details[field] is not None: date = Date() if field in encoded and encoded[field] is not None: - encoded_encoded = date.from_json(encoded[field]) - dt1 = CourseDetailsViewTest.struct_to_datetime(encoded_encoded) + dt1 = date.from_json(encoded[field]) if isinstance(details[field], datetime.datetime): dt2 = details[field] else: - details_encoded = date.from_json(details[field]) - dt2 = CourseDetailsViewTest.struct_to_datetime(details_encoded) + dt2 = date.from_json(details[field]) expected_delta = datetime.timedelta(0) self.assertEqual(dt1 - dt2, expected_delta, str(dt1) + "!=" + str(dt2) + " at " + context) diff --git a/cms/djangoapps/contentstore/views/assets.py b/cms/djangoapps/contentstore/views/assets.py index b5041d3e9f..2be2cfafb8 100644 --- a/cms/djangoapps/contentstore/views/assets.py +++ b/cms/djangoapps/contentstore/views/assets.py @@ -62,7 +62,7 @@ def asset_index(request, org, course, name): asset_id = asset['_id'] display_info = {} display_info['displayname'] = asset['displayname'] - display_info['uploadDate'] = get_default_time_display(asset['uploadDate'].timetuple()) + display_info['uploadDate'] = get_default_time_display(asset['uploadDate']) asset_location = StaticContent.compute_location(asset_id['org'], asset_id['course'], asset_id['name']) display_info['url'] = StaticContent.get_url_path_from_location(asset_location) @@ -131,7 +131,7 @@ def upload_asset(request, org, course, coursename): readback = contentstore().find(content.location) response_payload = {'displayname': content.name, - 'uploadDate': get_default_time_display(readback.last_modified_at.timetuple()), + 'uploadDate': get_default_time_display(readback.last_modified_at), 'url': StaticContent.get_url_path_from_location(content.location), 'thumb_url': StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_content is not None else None, 'msg': 'Upload completed' @@ -231,7 +231,7 @@ def generate_export_course(request, org, course, name): logging.debug('root = {0}'.format(root_dir)) export_to_xml(modulestore('direct'), contentstore(), loc, root_dir, name, modulestore()) - #filename = root_dir / name + '.tar.gz' + # filename = root_dir / name + '.tar.gz' logging.debug('tar file being generated at {0}'.format(export_file.name)) tar_file = tarfile.open(name=export_file.name, mode='w:gz') diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index 07f6b9669c..e1c176eebe 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -2,7 +2,6 @@ Views related to operations on course objects """ import json -import time from django.contrib.auth.decorators import login_required from django_future.csrf import ensure_csrf_cookie @@ -32,6 +31,8 @@ from .component import OPEN_ENDED_COMPONENT_TYPES, \ NOTE_COMPONENT_TYPES, ADVANCED_COMPONENT_POLICY_KEY from django_comment_common.utils import seed_permissions_roles +import datetime +from django.utils.timezone import UTC # TODO: should explicitly enumerate exports with __all__ @@ -130,7 +131,7 @@ def create_new_course(request): new_course.display_name = display_name # set a default start date to now - new_course.start = time.gmtime() + new_course.start = datetime.datetime.now(UTC()) initialize_course_tabs(new_course) @@ -357,49 +358,49 @@ def course_advanced_updates(request, org, course, name): # Whether or not to filter the tabs key out of the settings metadata filter_tabs = True - #Check to see if the user instantiated any advanced components. This is a hack - #that does the following : - # 1) adds/removes the open ended panel tab to a course automatically if the user + # Check to see if the user instantiated any advanced components. This is a hack + # that does the following : + # 1) adds/removes the open ended panel tab to a course automatically if the user # has indicated that they want to edit the combinedopendended or peergrading module # 2) adds/removes the notes panel tab to a course automatically if the user has # indicated that they want the notes module enabled in their course # TODO refactor the above into distinct advanced policy settings if ADVANCED_COMPONENT_POLICY_KEY in request_body: - #Get the course so that we can scrape current tabs + # Get the course so that we can scrape current tabs course_module = modulestore().get_item(location) - #Maps tab types to components + # Maps tab types to components tab_component_map = { - 'open_ended': OPEN_ENDED_COMPONENT_TYPES, + 'open_ended': OPEN_ENDED_COMPONENT_TYPES, 'notes': NOTE_COMPONENT_TYPES, } - #Check to see if the user instantiated any notes or open ended components + # Check to see if the user instantiated any notes or open ended components for tab_type in tab_component_map.keys(): component_types = tab_component_map.get(tab_type) found_ac_type = False for ac_type in component_types: if ac_type in request_body[ADVANCED_COMPONENT_POLICY_KEY]: - #Add tab to the course if needed + # Add tab to the course if needed changed, new_tabs = add_extra_panel_tab(tab_type, course_module) - #If a tab has been added to the course, then send the metadata along to CourseMetadata.update_from_json + # If a tab has been added to the course, then send the metadata along to CourseMetadata.update_from_json if changed: course_module.tabs = new_tabs request_body.update({'tabs': new_tabs}) - #Indicate that tabs should not be filtered out of the metadata + # Indicate that tabs should not be filtered out of the metadata filter_tabs = False - #Set this flag to avoid the tab removal code below. + # Set this flag to avoid the tab removal code below. found_ac_type = True break - #If we did not find a module type in the advanced settings, + # If we did not find a module type in the advanced settings, # we may need to remove the tab from the course. if not found_ac_type: - #Remove tab from the course if needed + # Remove tab from the course if needed changed, new_tabs = remove_extra_panel_tab(tab_type, course_module) if changed: course_module.tabs = new_tabs request_body.update({'tabs': new_tabs}) - #Indicate that tabs should *not* be filtered out of the metadata + # Indicate that tabs should *not* be filtered out of the metadata filter_tabs = False response_json = json.dumps(CourseMetadata.update_from_json(location, diff --git a/cms/djangoapps/models/settings/course_details.py b/cms/djangoapps/models/settings/course_details.py index 0dbb47b31b..28dba473f2 100644 --- a/cms/djangoapps/models/settings/course_details.py +++ b/cms/djangoapps/models/settings/course_details.py @@ -3,26 +3,26 @@ from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.inheritance import own_metadata import json from json.encoder import JSONEncoder -import time from contentstore.utils import get_modulestore from models.settings import course_grading from contentstore.utils import update_item from xmodule.fields import Date import re import logging +import datetime class CourseDetails(object): def __init__(self, location): - self.course_location = location # a Location obj + self.course_location = location # a Location obj self.start_date = None # 'start' - self.end_date = None # 'end' + self.end_date = None # 'end' self.enrollment_start = None self.enrollment_end = None - self.syllabus = None # a pdf file asset - self.overview = "" # html to render as the overview - self.intro_video = None # a video pointer - self.effort = None # int hours/week + self.syllabus = None # a pdf file asset + self.overview = "" # html to render as the overview + self.intro_video = None # a video pointer + self.effort = None # int hours/week @classmethod def fetch(cls, course_location): @@ -73,9 +73,9 @@ class CourseDetails(object): """ Decode the json into CourseDetails and save any changed attrs to the db """ - ## TODO make it an error for this to be undefined & for it to not be retrievable from modulestore + # # TODO make it an error for this to be undefined & for it to not be retrievable from modulestore course_location = jsondict['course_location'] - ## Will probably want to cache the inflight courses because every blur generates an update + # # Will probably want to cache the inflight courses because every blur generates an update descriptor = get_modulestore(course_location).get_item(course_location) dirty = False @@ -181,7 +181,7 @@ class CourseSettingsEncoder(json.JSONEncoder): return obj.__dict__ elif isinstance(obj, Location): return obj.dict() - elif isinstance(obj, time.struct_time): + elif isinstance(obj, datetime.datetime): return Date().to_json(obj) else: return JSONEncoder.default(self, obj) diff --git a/cms/templates/edit_subsection.html b/cms/templates/edit_subsection.html index 9bb9b3a506..4aae070ca1 100644 --- a/cms/templates/edit_subsection.html +++ b/cms/templates/edit_subsection.html @@ -1,7 +1,7 @@ <%inherit file="base.html" /> <%! import logging - from xmodule.util.date_utils import get_time_struct_display + from xmodule.util.date_utils import get_default_time_display %> <%! from django.core.urlresolvers import reverse %> @@ -36,11 +36,15 @@
- +
- +
% if subsection.lms.start != parent_item.lms.start and subsection.lms.start: @@ -48,7 +52,7 @@

The date above differs from the release date of ${parent_item.display_name_with_default}, which is unset. % else:

The date above differs from the release date of ${parent_item.display_name_with_default} – - ${get_time_struct_display(parent_item.lms.start, '%m/%d/%Y at %H:%M UTC')}. + ${get_default_time_display(parent_item.lms.start)}. % endif Sync to ${parent_item.display_name_with_default}.

% endif @@ -65,11 +69,15 @@
- +
- +
Remove due date
diff --git a/cms/templates/overview.html b/cms/templates/overview.html index d327c8b324..0b82d76943 100644 --- a/cms/templates/overview.html +++ b/cms/templates/overview.html @@ -1,7 +1,7 @@ <%inherit file="base.html" /> <%! import logging - from xmodule.util.date_utils import get_time_struct_display + from xmodule.util import date_utils %> <%! from django.core.urlresolvers import reverse %> <%block name="title">Course Outline @@ -154,14 +154,15 @@

<% - start_date_str = get_time_struct_display(section.lms.start, '%m/%d/%Y') - start_time_str = get_time_struct_display(section.lms.start, '%H:%M') + start_date_str = section.lms.start.strftime('%m/%d/%Y') + start_time_str = section.lms.start.strftime('%H:%M') %> %if section.lms.start is None: This section has not been released. Schedule %else: - Will Release: ${get_time_struct_display(section.lms.start, '%m/%d/%Y at %H:%M UTC')} + Will Release: + ${date_utils.get_default_time_display(section.lms.start)} Edit %endif
diff --git a/common/djangoapps/contentserver/middleware.py b/common/djangoapps/contentserver/middleware.py index 8e9e70046d..7deb0901aa 100644 --- a/common/djangoapps/contentserver/middleware.py +++ b/common/djangoapps/contentserver/middleware.py @@ -1,7 +1,4 @@ -import logging -import time - -from django.http import HttpResponse, Http404, HttpResponseNotModified +from django.http import HttpResponse, HttpResponseNotModified from xmodule.contentstore.django import contentstore from xmodule.contentstore.content import StaticContent, XASSET_LOCATION_TAG @@ -20,7 +17,7 @@ class StaticContentServer(object): # return a 'Bad Request' to browser as we have a malformed Location response = HttpResponse() response.status_code = 400 - return response + return response # first look in our cache so we don't have to round-trip to the DB content = get_cached_content(loc) diff --git a/common/djangoapps/student/management/commands/pearson_make_tc_registration.py b/common/djangoapps/student/management/commands/pearson_make_tc_registration.py index b10cf143a0..50e56bb4be 100644 --- a/common/djangoapps/student/management/commands/pearson_make_tc_registration.py +++ b/common/djangoapps/student/management/commands/pearson_make_tc_registration.py @@ -1,5 +1,4 @@ from optparse import make_option -from time import strftime from django.contrib.auth.models import User from django.core.management.base import BaseCommand, CommandError @@ -128,8 +127,8 @@ class Command(BaseCommand): exam = CourseDescriptor.TestCenterExam(course_id, exam_name, exam_info) # update option values for date_first and date_last to use YYYY-MM-DD format # instead of YYYY-MM-DDTHH:MM - our_options['eligibility_appointment_date_first'] = strftime("%Y-%m-%d", exam.first_eligible_appointment_date) - our_options['eligibility_appointment_date_last'] = strftime("%Y-%m-%d", exam.last_eligible_appointment_date) + our_options['eligibility_appointment_date_first'] = exam.first_eligible_appointment_date.strftime("%Y-%m-%d") + our_options['eligibility_appointment_date_last'] = exam.last_eligible_appointment_date.strftime("%Y-%m-%d") if exam is None: raise CommandError("Exam for course_id {} does not exist".format(course_id)) diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index ab68b05f4b..57f3d756b9 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -16,7 +16,6 @@ import json import logging import uuid from random import randint -from time import strftime from django.conf import settings @@ -54,7 +53,7 @@ class UserProfile(models.Model): class Meta: db_table = "auth_userprofile" - ## CRITICAL TODO/SECURITY + # # CRITICAL TODO/SECURITY # Sanitize all fields. # This is not visible to other users, but could introduce holes later user = models.OneToOneField(User, unique=True, db_index=True, related_name='profile') @@ -429,8 +428,8 @@ class TestCenterRegistration(models.Model): registration.course_id = exam.course_id registration.accommodation_request = accommodation_request.strip() registration.exam_series_code = exam.exam_series_code - registration.eligibility_appointment_date_first = strftime("%Y-%m-%d", exam.first_eligible_appointment_date) - registration.eligibility_appointment_date_last = strftime("%Y-%m-%d", exam.last_eligible_appointment_date) + registration.eligibility_appointment_date_first = exam.first_eligible_appointment_date.strftime("%Y-%m-%d") + registration.eligibility_appointment_date_last = exam.last_eligible_appointment_date.strftime("%Y-%m-%d") registration.client_authorization_id = cls._create_client_authorization_id() # accommodation_code remains blank for now, along with Pearson confirmation information return registration @@ -598,7 +597,7 @@ def unique_id_for_user(user): return h.hexdigest() -## TODO: Should be renamed to generic UserGroup, and possibly +# # TODO: Should be renamed to generic UserGroup, and possibly # Given an optional field for type of group class UserTestGroup(models.Model): users = models.ManyToManyField(User, db_index=True) @@ -626,7 +625,7 @@ class Registration(models.Model): def activate(self): self.user.is_active = True self.user.save() - #self.delete() + # self.delete() class PendingNameChange(models.Model): @@ -648,7 +647,7 @@ class CourseEnrollment(models.Model): created = models.DateTimeField(auto_now_add=True, null=True, db_index=True) class Meta: - unique_together = (('user', 'course_id'), ) + unique_together = (('user', 'course_id'),) def __unicode__(self): return "[CourseEnrollment] %s: %s (%s)" % (self.user, self.course_id, self.created) @@ -667,12 +666,12 @@ class CourseEnrollmentAllowed(models.Model): created = models.DateTimeField(auto_now_add=True, null=True, db_index=True) class Meta: - unique_together = (('email', 'course_id'), ) + unique_together = (('email', 'course_id'),) def __unicode__(self): return "[CourseEnrollmentAllowed] %s: %s (%s)" % (self.email, self.course_id, self.created) -#cache_relation(User.profile) +# cache_relation(User.profile) #### Helper methods for use from python manage.py shell and other classes. diff --git a/common/djangoapps/xmodule_modifiers.py b/common/djangoapps/xmodule_modifiers.py index 45691cd854..570b38c942 100644 --- a/common/djangoapps/xmodule_modifiers.py +++ b/common/djangoapps/xmodule_modifiers.py @@ -1,7 +1,6 @@ import re import json import logging -import time import static_replace from django.conf import settings @@ -9,6 +8,8 @@ from functools import wraps from mitxmako.shortcuts import render_to_string from xmodule.seq_module import SequenceModule from xmodule.vertical_module import VerticalModule +import datetime +from django.utils.timezone import UTC log = logging.getLogger("mitx.xmodule_modifiers") @@ -83,7 +84,7 @@ def grade_histogram(module_id): cursor.execute(q, [module_id]) grades = list(cursor.fetchall()) - grades.sort(key=lambda x: x[0]) # Add ORDER BY to sql query? + grades.sort(key=lambda x: x[0]) # Add ORDER BY to sql query? if len(grades) >= 1 and grades[0][0] is None: return [] return grades @@ -101,7 +102,7 @@ def add_histogram(get_html, module, user): @wraps(get_html) def _get_html(): - if type(module) in [SequenceModule, VerticalModule]: # TODO: make this more general, eg use an XModule attribute instead + if type(module) in [SequenceModule, VerticalModule]: # TODO: make this more general, eg use an XModule attribute instead return get_html() module_id = module.id @@ -132,7 +133,7 @@ def add_histogram(get_html, module, user): # useful to indicate to staff if problem has been released or not # TODO (ichuang): use _has_access_descriptor.can_load in lms.courseware.access, instead of now>mstart comparison here - now = time.gmtime() + now = datetime.datetime.now(UTC()) is_released = "unknown" mstart = module.descriptor.lms.start diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 65280d6d29..3680379406 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -144,11 +144,11 @@ class InputTypeBase(object): self.tag = xml.tag self.system = system - ## NOTE: ID should only come from one place. If it comes from multiple, - ## we use state first, XML second (in case the xml changed, but we have - ## existing state with an old id). Since we don't make this guarantee, - ## we can swap this around in the future if there's a more logical - ## order. + # # NOTE: ID should only come from one place. If it comes from multiple, + # # we use state first, XML second (in case the xml changed, but we have + # # existing state with an old id). Since we don't make this guarantee, + # # we can swap this around in the future if there's a more logical + # # order. self.input_id = state.get('id', xml.get('id')) if self.input_id is None: @@ -769,7 +769,7 @@ class MatlabInput(CodeInput): # construct xqueue headers qinterface = self.system.xqueue['interface'] - qtime = datetime.strftime(datetime.utcnow(), xqueue_interface.dateformat) + qtime = datetime.utcnow().strftime(xqueue_interface.dateformat) callback_url = self.system.xqueue['construct_callback']('ungraded_response') anonymous_student_id = self.system.anonymous_student_id queuekey = xqueue_interface.make_hashkey(str(self.system.seed) + qtime + diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index 9e0ab16203..51b20c12ea 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -11,7 +11,7 @@ import sys from pkg_resources import resource_string from capa.capa_problem import LoncapaProblem -from capa.responsetypes import StudentInputError,\ +from capa.responsetypes import StudentInputError, \ ResponseError, LoncapaProblemError from capa.util import convert_files_to_filenames from .progress import Progress @@ -20,7 +20,7 @@ from xmodule.raw_module import RawDescriptor from xmodule.exceptions import NotFoundError, ProcessingError from xblock.core import Scope, String, Boolean, Object from .fields import Timedelta, Date, StringyInteger, StringyFloat -from xmodule.util.date_utils import time_to_datetime +from django.utils.timezone import UTC log = logging.getLogger("mitx.courseware") @@ -134,7 +134,7 @@ class CapaModule(CapaFields, XModule): def __init__(self, system, location, descriptor, model_data): XModule.__init__(self, system, location, descriptor, model_data) - due_date = time_to_datetime(self.due) + due_date = self.due if self.graceperiod is not None and due_date: self.close_date = due_date + self.graceperiod @@ -502,7 +502,7 @@ class CapaModule(CapaFields, XModule): Is it now past this problem's due date, including grace period? """ return (self.close_date is not None and - datetime.datetime.utcnow() > self.close_date) + datetime.datetime.now(UTC()) > self.close_date) def closed(self): ''' Is the student still allowed to submit answers? ''' diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 063e53aef4..66d53b43ec 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -4,7 +4,6 @@ from math import exp from lxml import etree from path import path # NOTE (THK): Only used for detecting presence of syllabus import requests -import time from datetime import datetime import dateutil.parser @@ -14,11 +13,11 @@ from xmodule.seq_module import SequenceDescriptor, SequenceModule from xmodule.timeparse import parse_time from xmodule.util.decorators import lazyproperty from xmodule.graders import grader_from_conf -from xmodule.util.date_utils import time_to_datetime import json from xblock.core import Scope, List, String, Object, Boolean from .fields import Date +from django.utils.timezone import UTC log = logging.getLogger(__name__) @@ -219,8 +218,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): msg = None if self.start is None: msg = "Course loaded without a valid start date. id = %s" % self.id - # hack it -- start in 1970 - self.start = time.gmtime(0) + self.start = datetime.now(UTC()) log.critical(msg) self.system.error_tracker(msg) @@ -392,7 +390,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): textbook_xml_object.set('book_url', textbook.book_url) xml_object.append(textbook_xml_object) - + return xml_object def has_ended(self): @@ -403,10 +401,10 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): if self.end is None: return False - return time.gmtime() > self.end + return datetime.now(UTC()) > self.end def has_started(self): - return time.gmtime() > self.start + return datetime.now(UTC()) > self.start @property def grader(self): @@ -547,14 +545,16 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): announcement = self.announcement if announcement is not None: - announcement = time_to_datetime(announcement) + announcement = announcement try: start = dateutil.parser.parse(self.advertised_start) + if start.tzinfo is None: + start = start.replace(tzinfo=UTC()) except (ValueError, AttributeError): - start = time_to_datetime(self.start) + start = self.start - now = datetime.utcnow() + now = datetime.now(UTC()) return announcement, start, now @@ -656,7 +656,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): elif self.advertised_start is None and self.start is None: return 'TBD' else: - return time.strftime("%b %d, %Y", self.advertised_start or self.start) + return (self.advertised_start or self.start).strftime("%b %d, %Y") @property def end_date_text(self): @@ -665,7 +665,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): If the course does not have an end date set (course.end is None), an empty string will be returned. """ - return '' if self.end is None else time.strftime("%b %d, %Y", self.end) + return '' if self.end is None else self.end.strftime("%b %d, %Y") @property def forum_posts_allowed(self): @@ -673,7 +673,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): blackout_periods = [(parse_time(start), parse_time(end)) for start, end in self.discussion_blackouts] - now = time.gmtime() + now = datetime.now(UTC()) for start, end in blackout_periods: if start <= now <= end: return False @@ -699,7 +699,8 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): self.last_eligible_appointment_date = self._try_parse_time('Last_Eligible_Appointment_Date') # or self.first_eligible_appointment_date if self.last_eligible_appointment_date is None: raise ValueError("Last appointment date must be specified") - self.registration_start_date = self._try_parse_time('Registration_Start_Date') or time.gmtime(0) + self.registration_start_date = (self._try_parse_time('Registration_Start_Date') or + datetime.utcfromtimestamp(0)) self.registration_end_date = self._try_parse_time('Registration_End_Date') or self.last_eligible_appointment_date # do validation within the exam info: if self.registration_start_date > self.registration_end_date: @@ -725,32 +726,32 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): return None def has_started(self): - return time.gmtime() > self.first_eligible_appointment_date + return datetime.now(UTC()) > self.first_eligible_appointment_date def has_ended(self): - return time.gmtime() > self.last_eligible_appointment_date + return datetime.now(UTC()) > self.last_eligible_appointment_date def has_started_registration(self): - return time.gmtime() > self.registration_start_date + return datetime.now(UTC()) > self.registration_start_date def has_ended_registration(self): - return time.gmtime() > self.registration_end_date + return datetime.now(UTC()) > self.registration_end_date def is_registering(self): - now = time.gmtime() + now = datetime.now(UTC()) return now >= self.registration_start_date and now <= self.registration_end_date @property def first_eligible_appointment_date_text(self): - return time.strftime("%b %d, %Y", self.first_eligible_appointment_date) + return datetime.strftime("%b %d, %Y", self.first_eligible_appointment_date) @property def last_eligible_appointment_date_text(self): - return time.strftime("%b %d, %Y", self.last_eligible_appointment_date) + return datetime.strftime("%b %d, %Y", self.last_eligible_appointment_date) @property def registration_end_date_text(self): - return time.strftime("%b %d, %Y at %H:%M UTC", self.registration_end_date) + return datetime.strftime("%b %d, %Y at %H:%M UTC", self.registration_end_date) @property def current_test_center_exam(self): diff --git a/common/lib/xmodule/xmodule/fields.py b/common/lib/xmodule/xmodule/fields.py index 3d56b7941e..3164c062bb 100644 --- a/common/lib/xmodule/xmodule/fields.py +++ b/common/lib/xmodule/xmodule/fields.py @@ -2,19 +2,19 @@ import time import logging import re -from datetime import timedelta from xblock.core import ModelType import datetime import dateutil.parser from xblock.core import Integer, Float, Boolean +from django.utils.timezone import UTC log = logging.getLogger(__name__) class Date(ModelType): ''' - Date fields know how to parse and produce json (iso) compatible formats. + Date fields know how to parse and produce json (iso) compatible formats. Converts to tz aware datetimes. ''' def from_json(self, field): """ @@ -27,11 +27,15 @@ class Date(ModelType): elif field is "": return None elif isinstance(field, basestring): - d = dateutil.parser.parse(field) - return d.utctimetuple() + result = dateutil.parser.parse(field) + if result.tzinfo is None: + result = result.replace(tzinfo=UTC()) + return result elif isinstance(field, (int, long, float)): - return time.gmtime(field / 1000) + return datetime.datetime.fromtimestamp(field / 1000, UTC()) elif isinstance(field, time.struct_time): + return datetime.datetime.fromtimestamp(time.mktime(field), UTC()) + elif isinstance(field, datetime.datetime): return field else: msg = "Field {0} has bad value '{1}'".format( @@ -49,7 +53,11 @@ class Date(ModelType): # struct_times are always utc return time.strftime('%Y-%m-%dT%H:%M:%SZ', value) elif isinstance(value, datetime.datetime): - return value.isoformat() + 'Z' + if value.tzinfo is None or value.utcoffset().total_seconds() == 0: + # isoformat adds +00:00 rather than Z + return value.strftime('%Y-%m-%dT%H:%M:%SZ') + else: + return value.isoformat() TIMEDELTA_REGEX = re.compile(r'^((?P\d+?) day(?:s?))?(\s)?((?P\d+?) hour(?:s?))?(\s)?((?P\d+?) minute(?:s)?)?(\s)?((?P\d+?) second(?:s)?)?$') @@ -74,7 +82,7 @@ class Timedelta(ModelType): for (name, param) in parts.iteritems(): if param: time_params[name] = int(param) - return timedelta(**time_params) + return datetime.timedelta(**time_params) def to_json(self, value): values = [] @@ -93,7 +101,7 @@ class StringyInteger(Integer): def from_json(self, value): try: return int(value) - except: + except Exception: return None diff --git a/common/lib/xmodule/xmodule/foldit_module.py b/common/lib/xmodule/xmodule/foldit_module.py index 62c5ea416e..5ab1b327c6 100644 --- a/common/lib/xmodule/xmodule/foldit_module.py +++ b/common/lib/xmodule/xmodule/foldit_module.py @@ -8,7 +8,6 @@ from xmodule.x_module import XModule from xmodule.xml_module import XmlDescriptor from xblock.core import Scope, Integer, String from .fields import Date -from xmodule.util.date_utils import time_to_datetime log = logging.getLogger(__name__) @@ -31,9 +30,7 @@ class FolditModule(FolditFields, XModule): css = {'scss': [resource_string(__name__, 'css/foldit/leaderboard.scss')]} def __init__(self, *args, **kwargs): - XModule.__init__(self, *args, **kwargs) """ - Example: """ - - self.due_time = time_to_datetime(self.due) + XModule.__init__(self, *args, **kwargs) + self.due_time = self.due def is_complete(self): """ @@ -102,7 +99,7 @@ class FolditModule(FolditFields, XModule): from foldit.models import Score leaders = [(e['username'], e['score']) for e in Score.get_tops_n(10)] - leaders.sort(key=lambda x: -x[1]) + leaders.sort(key=lambda x:-x[1]) return leaders diff --git a/common/lib/xmodule/xmodule/modulestore/draft.py b/common/lib/xmodule/xmodule/modulestore/draft.py index c16c7403a9..048aea8867 100644 --- a/common/lib/xmodule/xmodule/modulestore/draft.py +++ b/common/lib/xmodule/xmodule/modulestore/draft.py @@ -4,6 +4,7 @@ from . import ModuleStoreBase, Location, namedtuple_to_son from .exceptions import ItemNotFoundError from .inheritance import own_metadata from xmodule.exceptions import InvalidVersionError +from pytz import UTC DRAFT = 'draft' # Things w/ these categories should never be marked as version='draft' @@ -197,7 +198,7 @@ class DraftModuleStore(ModuleStoreBase): """ draft = self.get_item(location) - draft.cms.published_date = datetime.utcnow() + draft.cms.published_date = datetime.now(UTC) draft.cms.published_by = published_by_id super(DraftModuleStore, self).update_item(location, draft._model_data._kvs._data) super(DraftModuleStore, self).update_children(location, draft._model_data._kvs._children) diff --git a/common/lib/xmodule/xmodule/modulestore/xml.py b/common/lib/xmodule/xmodule/modulestore/xml.py index 4ea83d7e11..6ab6843216 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml.py +++ b/common/lib/xmodule/xmodule/modulestore/xml.py @@ -52,7 +52,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem): xmlstore: the XMLModuleStore to store the loaded modules in """ - self.unnamed = defaultdict(int) # category -> num of new url_names for that category + self.unnamed = defaultdict(int) # category -> num of new url_names for that category self.used_names = defaultdict(set) # category -> set of used url_names self.org, self.course, self.url_name = course_id.split('/') # cdodge: adding the course_id as passed in for later reference rather than having to recomine the org/course/url_name @@ -124,7 +124,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem): else: # TODO (vshnayder): We may want to enable this once course repos are cleaned up. # (or we may want to give up on the requirement for non-state-relevant issues...) - #error_tracker("WARNING: no name specified for module. xml='{0}...'".format(xml[:100])) + # error_tracker("WARNING: no name specified for module. xml='{0}...'".format(xml[:100])) pass # Make sure everything is unique @@ -447,7 +447,7 @@ class XMLModuleStore(ModuleStoreBase): def load_extra_content(self, system, course_descriptor, category, base_dir, course_dir, url_name): self._load_extra_content(system, course_descriptor, category, base_dir, course_dir) - # then look in a override folder based on the course run + # then look in a override folder based on the course run if os.path.isdir(base_dir / url_name): self._load_extra_content(system, course_descriptor, category, base_dir / url_name, course_dir) 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 7dc8d99451..b5d4e1b676 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py @@ -16,6 +16,7 @@ from .peer_grading_service import PeerGradingService, MockPeerGradingService import controller_query_service from datetime import datetime +from django.utils.timezone import UTC log = logging.getLogger("mitx.courseware") @@ -56,7 +57,7 @@ class OpenEndedChild(object): POST_ASSESSMENT = 'post_assessment' DONE = 'done' - #This is used to tell students where they are at in the module + # This is used to tell students where they are at in the module HUMAN_NAMES = { 'initial': 'Not started', 'assessing': 'In progress', @@ -102,7 +103,7 @@ class OpenEndedChild(object): if system.open_ended_grading_interface: self.peer_gs = PeerGradingService(system.open_ended_grading_interface, system) self.controller_qs = controller_query_service.ControllerQueryService( - system.open_ended_grading_interface,system + system.open_ended_grading_interface, system ) else: self.peer_gs = MockPeerGradingService() @@ -130,7 +131,7 @@ class OpenEndedChild(object): pass def closed(self): - if self.close_date is not None and datetime.utcnow() > self.close_date: + if self.close_date is not None and datetime.now(UTC()) > self.close_date: return True return False @@ -138,13 +139,13 @@ class OpenEndedChild(object): if self.closed(): return True, { 'success': False, - #This is a student_facing_error + # This is a student_facing_error 'error': 'The problem close date has passed, and this problem is now closed.' } elif self.child_attempts > self.max_attempts: return True, { 'success': False, - #This is a student_facing_error + # This is a student_facing_error 'error': 'You have attempted this problem {0} times. You are allowed {1} attempts.'.format( self.child_attempts, self.max_attempts ) @@ -272,7 +273,7 @@ class OpenEndedChild(object): try: return Progress(int(self.get_score()['score']), int(self._max_score)) except Exception as err: - #This is a dev_facing_error + # This is a dev_facing_error log.exception("Got bad progress from open ended child module. Max Score: {0}".format(self._max_score)) return None return None @@ -281,10 +282,10 @@ class OpenEndedChild(object): """ return dict out-of-sync error message, and also log. """ - #This is a dev_facing_error + # This is a dev_facing_error log.warning("Open ended child state out sync. state: %r, get: %r. %s", self.child_state, get, msg) - #This is a student_facing_error + # This is a student_facing_error return {'success': False, 'error': 'The problem state got out-of-sync. Please try reloading the page.'} @@ -391,7 +392,7 @@ class OpenEndedChild(object): """ overall_success = False if not self.accept_file_upload: - #If the question does not accept file uploads, do not do anything + # If the question does not accept file uploads, do not do anything return True, get_data has_file_to_upload, uploaded_to_s3, image_ok, image_tag = self.check_for_image_and_upload(get_data) @@ -399,19 +400,19 @@ class OpenEndedChild(object): get_data['student_answer'] += image_tag overall_success = True elif has_file_to_upload and not uploaded_to_s3 and image_ok: - #In this case, an image was submitted by the student, but the image could not be uploaded to S3. Likely - #a config issue (development vs deployment). For now, just treat this as a "success" + # In this case, an image was submitted by the student, but the image could not be uploaded to S3. Likely + # a config issue (development vs deployment). For now, just treat this as a "success" log.exception("Student AJAX post to combined open ended xmodule indicated that it contained an image, " "but the image was not able to be uploaded to S3. This could indicate a config" "issue with this deployment, but it could also indicate a problem with S3 or with the" "student image itself.") overall_success = True elif not has_file_to_upload: - #If there is no file to upload, probably the student has embedded the link in the answer text + # If there is no file to upload, probably the student has embedded the link in the answer text success, get_data['student_answer'] = self.check_for_url_in_text(get_data['student_answer']) overall_success = success - #log.debug("Has file: {0} Uploaded: {1} Image Ok: {2}".format(has_file_to_upload, uploaded_to_s3, image_ok)) + # log.debug("Has file: {0} Uploaded: {1} Image Ok: {2}".format(has_file_to_upload, uploaded_to_s3, image_ok)) return overall_success, get_data @@ -441,7 +442,7 @@ class OpenEndedChild(object): success = False allowed_to_submit = True response = {} - #This is a student_facing_error + # This is a student_facing_error error_string = ("You need to peer grade {0} more in order to make another submission. " "You have graded {1}, and {2} are required. You have made {3} successful peer grading submissions.") try: @@ -451,17 +452,17 @@ class OpenEndedChild(object): student_sub_count = response['student_sub_count'] success = True except: - #This is a dev_facing_error + # This is a dev_facing_error log.error("Could not contact external open ended graders for location {0} and student {1}".format( self.location_string, student_id)) - #This is a student_facing_error + # This is a student_facing_error error_message = "Could not contact the graders. Please notify course staff." return success, allowed_to_submit, error_message if count_graded >= count_required: return success, allowed_to_submit, "" else: allowed_to_submit = False - #This is a student_facing_error + # This is a student_facing_error error_message = error_string.format(count_required - count_graded, count_graded, count_required, student_sub_count) return success, allowed_to_submit, error_message diff --git a/common/lib/xmodule/xmodule/peer_grading_module.py b/common/lib/xmodule/xmodule/peer_grading_module.py index ccc3e31f51..d0d6ef9242 100644 --- a/common/lib/xmodule/xmodule/peer_grading_module.py +++ b/common/lib/xmodule/xmodule/peer_grading_module.py @@ -15,6 +15,7 @@ from xmodule.fields import Date, StringyFloat, StringyInteger, StringyBoolean from xmodule.open_ended_grading_classes.peer_grading_service import PeerGradingService, GradingServiceError, MockPeerGradingService from open_ended_grading_classes import combined_open_ended_rubric +from django.utils.timezone import UTC log = logging.getLogger(__name__) @@ -76,7 +77,7 @@ class PeerGradingModule(PeerGradingFields, XModule): def __init__(self, system, location, descriptor, model_data): XModule.__init__(self, system, location, descriptor, model_data) - #We need to set the location here so the child modules can use it + # We need to set the location here so the child modules can use it system.set('location', location) self.system = system if (self.system.open_ended_grading_interface): @@ -112,7 +113,7 @@ class PeerGradingModule(PeerGradingFields, XModule): if not self.ajax_url.endswith("/"): self.ajax_url = self.ajax_url + "/" - #StringyInteger could return None, so keep this check. + # StringyInteger could return None, so keep this check. if not isinstance(self.max_grade, int): raise TypeError("max_grade needs to be an integer.") @@ -120,7 +121,7 @@ class PeerGradingModule(PeerGradingFields, XModule): return self._closed(self.timeinfo) def _closed(self, timeinfo): - if timeinfo.close_date is not None and datetime.utcnow() > timeinfo.close_date: + if timeinfo.close_date is not None and datetime.now(UTC()) > timeinfo.close_date: return True return False @@ -166,9 +167,9 @@ class PeerGradingModule(PeerGradingFields, XModule): } if dispatch not in handlers: - #This is a dev_facing_error + # This is a dev_facing_error log.error("Cannot find {0} in handlers in handle_ajax function for open_ended_module.py".format(dispatch)) - #This is a dev_facing_error + # This is a dev_facing_error return json.dumps({'error': 'Error handling action. Please try again.', 'success': False}) d = handlers[dispatch](get) @@ -187,7 +188,7 @@ class PeerGradingModule(PeerGradingFields, XModule): count_required = response['count_required'] success = True except GradingServiceError: - #This is a dev_facing_error + # This is a dev_facing_error log.exception("Error getting location data from controller for location {0}, student {1}" .format(location, student_id)) @@ -220,7 +221,7 @@ class PeerGradingModule(PeerGradingFields, XModule): count_graded = response['count_graded'] count_required = response['count_required'] if count_required > 0 and count_graded >= count_required: - #Ensures that once a student receives a final score for peer grading, that it does not change. + # Ensures that once a student receives a final score for peer grading, that it does not change. self.student_data_for_location = response if self.weight is not None: @@ -271,10 +272,10 @@ class PeerGradingModule(PeerGradingFields, XModule): response = self.peer_gs.get_next_submission(location, grader_id) return response except GradingServiceError: - #This is a dev_facing_error + # This is a dev_facing_error log.exception("Error getting next submission. server url: {0} location: {1}, grader_id: {2}" .format(self.peer_gs.url, location, grader_id)) - #This is a student_facing_error + # This is a student_facing_error return {'success': False, 'error': EXTERNAL_GRADER_NO_CONTACT_ERROR} @@ -314,13 +315,13 @@ class PeerGradingModule(PeerGradingFields, XModule): score, feedback, submission_key, rubric_scores, submission_flagged) return response except GradingServiceError: - #This is a dev_facing_error + # This is a dev_facing_error log.exception("""Error saving grade to open ended grading service. server url: {0}, location: {1}, submission_id:{2}, submission_key: {3}, score: {4}""" .format(self.peer_gs.url, location, submission_id, submission_key, score) ) - #This is a student_facing_error + # This is a student_facing_error return { 'success': False, 'error': EXTERNAL_GRADER_NO_CONTACT_ERROR @@ -356,10 +357,10 @@ class PeerGradingModule(PeerGradingFields, XModule): response = self.peer_gs.is_student_calibrated(location, grader_id) return response except GradingServiceError: - #This is a dev_facing_error + # This is a dev_facing_error log.exception("Error from open ended grading service. server url: {0}, grader_id: {0}, location: {1}" .format(self.peer_gs.url, grader_id, location)) - #This is a student_facing_error + # This is a student_facing_error return { 'success': False, 'error': EXTERNAL_GRADER_NO_CONTACT_ERROR @@ -401,17 +402,17 @@ class PeerGradingModule(PeerGradingFields, XModule): response = self.peer_gs.show_calibration_essay(location, grader_id) return response except GradingServiceError: - #This is a dev_facing_error + # This is a dev_facing_error log.exception("Error from open ended grading service. server url: {0}, location: {0}" .format(self.peer_gs.url, location)) - #This is a student_facing_error + # This is a student_facing_error return {'success': False, 'error': EXTERNAL_GRADER_NO_CONTACT_ERROR} # if we can't parse the rubric into HTML, except etree.XMLSyntaxError: - #This is a dev_facing_error + # This is a dev_facing_error log.exception("Cannot parse rubric string.") - #This is a student_facing_error + # This is a student_facing_error return {'success': False, 'error': 'Error displaying submission. Please notify course staff.'} @@ -455,11 +456,11 @@ class PeerGradingModule(PeerGradingFields, XModule): response['actual_rubric'] = rubric_renderer.render_rubric(response['actual_rubric'])['html'] return response except GradingServiceError: - #This is a dev_facing_error + # This is a dev_facing_error log.exception( "Error saving calibration grade, location: {0}, submission_key: {1}, grader_id: {2}".format( location, submission_key, grader_id)) - #This is a student_facing_error + # This is a student_facing_error return self._err_response('There was an error saving your score. Please notify course staff.') def peer_grading_closed(self): @@ -491,13 +492,13 @@ class PeerGradingModule(PeerGradingFields, XModule): problem_list = problem_list_dict['problem_list'] except GradingServiceError: - #This is a student_facing_error + # This is a student_facing_error error_text = EXTERNAL_GRADER_NO_CONTACT_ERROR log.error(error_text) success = False # catch error if if the json loads fails except ValueError: - #This is a student_facing_error + # This is a student_facing_error error_text = "Could not get list of problems to peer grade. Please notify course staff." log.error(error_text) success = False @@ -557,8 +558,8 @@ class PeerGradingModule(PeerGradingFields, XModule): ''' if get is None or get.get('location') is None: if self.use_for_single_location not in TRUE_DICT: - #This is an error case, because it must be set to use a single location to be called without get parameters - #This is a dev_facing_error + # This is an error case, because it must be set to use a single location to be called without get parameters + # This is a dev_facing_error log.error( "Peer grading problem in peer_grading_module called with no get parameters, but use_for_single_location is False.") return {'html': "", 'success': False} diff --git a/common/lib/xmodule/xmodule/tests/test_course_module.py b/common/lib/xmodule/xmodule/tests/test_course_module.py index 0d789964e9..53181b5a28 100644 --- a/common/lib/xmodule/xmodule/tests/test_course_module.py +++ b/common/lib/xmodule/xmodule/tests/test_course_module.py @@ -1,5 +1,4 @@ import unittest -from time import strptime import datetime from fs.memoryfs import MemoryFS @@ -8,13 +7,13 @@ from mock import Mock, patch from xmodule.modulestore.xml import ImportSystem, XMLModuleStore import xmodule.course_module -from xmodule.util.date_utils import time_to_datetime +from django.utils.timezone import UTC ORG = 'test_org' COURSE = 'test_course' -NOW = strptime('2013-01-01T01:00:00', '%Y-%m-%dT%H:%M:00') +NOW = datetime.datetime.strptime('2013-01-01T01:00:00', '%Y-%m-%dT%H:%M:00').replace(tzinfo=UTC()) class DummySystem(ImportSystem): @@ -81,10 +80,10 @@ class IsNewCourseTestCase(unittest.TestCase): Mock(wraps=datetime.datetime) ) mocked_datetime = datetime_patcher.start() - mocked_datetime.utcnow.return_value = time_to_datetime(NOW) + mocked_datetime.now.return_value = NOW self.addCleanup(datetime_patcher.stop) - @patch('xmodule.course_module.time.gmtime') + @patch('xmodule.course_module.datetime.now') def test_sorting_score(self, gmtime_mock): gmtime_mock.return_value = NOW @@ -125,7 +124,7 @@ class IsNewCourseTestCase(unittest.TestCase): print "Comparing %s to %s" % (a, b) assertion(a_score, b_score) - @patch('xmodule.course_module.time.gmtime') + @patch('xmodule.course_module.datetime.now') def test_start_date_text(self, gmtime_mock): gmtime_mock.return_value = NOW diff --git a/common/lib/xmodule/xmodule/tests/test_date_utils.py b/common/lib/xmodule/xmodule/tests/test_date_utils.py deleted file mode 100644 index af96de018f..0000000000 --- a/common/lib/xmodule/xmodule/tests/test_date_utils.py +++ /dev/null @@ -1,35 +0,0 @@ -# Tests for xmodule.util.date_utils - -from nose.tools import assert_equals -from xmodule.util import date_utils -import datetime -import time - - -def test_get_time_struct_display(): - assert_equals("", date_utils.get_time_struct_display(None, "")) - test_time = time.struct_time((1992, 3, 12, 15, 3, 30, 1, 71, 0)) - assert_equals("03/12/1992", date_utils.get_time_struct_display(test_time, '%m/%d/%Y')) - assert_equals("15:03", date_utils.get_time_struct_display(test_time, '%H:%M')) - - -def test_get_default_time_display(): - assert_equals("", date_utils.get_default_time_display(None)) - test_time = time.struct_time((1992, 3, 12, 15, 3, 30, 1, 71, 0)) - assert_equals( - "Mar 12, 1992 at 15:03 UTC", - date_utils.get_default_time_display(test_time)) - assert_equals( - "Mar 12, 1992 at 15:03 UTC", - date_utils.get_default_time_display(test_time, True)) - assert_equals( - "Mar 12, 1992 at 15:03", - date_utils.get_default_time_display(test_time, False)) - - -def test_time_to_datetime(): - assert_equals(None, date_utils.time_to_datetime(None)) - test_time = time.struct_time((1992, 3, 12, 15, 3, 30, 1, 71, 0)) - assert_equals( - datetime.datetime(1992, 3, 12, 15, 3, 30), - date_utils.time_to_datetime(test_time)) diff --git a/common/lib/xmodule/xmodule/tests/test_fields.py b/common/lib/xmodule/xmodule/tests/test_fields.py index 9642f7c595..1b6c86c000 100644 --- a/common/lib/xmodule/xmodule/tests/test_fields.py +++ b/common/lib/xmodule/xmodule/tests/test_fields.py @@ -1,24 +1,15 @@ """Tests for classes defined in fields.py.""" import datetime import unittest -from django.utils.timezone import UTC from xmodule.fields import Date, StringyFloat, StringyInteger, StringyBoolean -import time +from django.utils.timezone import UTC class DateTest(unittest.TestCase): date = Date() - @staticmethod - def struct_to_datetime(struct_time): - return datetime.datetime(struct_time.tm_year, struct_time.tm_mon, - struct_time.tm_mday, struct_time.tm_hour, - struct_time.tm_min, struct_time.tm_sec, tzinfo=UTC()) - - def compare_dates(self, date1, date2, expected_delta): - dt1 = DateTest.struct_to_datetime(date1) - dt2 = DateTest.struct_to_datetime(date2) - self.assertEqual(dt1 - dt2, expected_delta, str(date1) + "-" - + str(date2) + "!=" + str(expected_delta)) + def compare_dates(self, dt1, dt2, expected_delta): + self.assertEqual(dt1 - dt2, expected_delta, str(dt1) + "-" + + str(dt2) + "!=" + str(expected_delta)) def test_from_json(self): '''Test conversion from iso compatible date strings to struct_time''' @@ -55,10 +46,10 @@ class DateTest(unittest.TestCase): def test_old_due_date_format(self): current = datetime.datetime.today() self.assertEqual( - time.struct_time((current.year, 3, 12, 12, 0, 0, 1, 71, 0)), + datetime.datetime(current.year, 3, 12, 12, tzinfo=UTC()), DateTest.date.from_json("March 12 12:00")) self.assertEqual( - time.struct_time((current.year, 12, 4, 16, 30, 0, 2, 338, 0)), + datetime.datetime(current.year, 12, 4, 16, 30, tzinfo=UTC()), DateTest.date.from_json("December 4 16:30")) def test_to_json(self): @@ -67,7 +58,7 @@ class DateTest(unittest.TestCase): ''' self.assertEqual( DateTest.date.to_json( - time.strptime("2012-12-31T23:59:59Z", "%Y-%m-%dT%H:%M:%SZ")), + datetime.datetime.strptime("2012-12-31T23:59:59Z", "%Y-%m-%dT%H:%M:%SZ")), "2012-12-31T23:59:59Z") self.assertEqual( DateTest.date.to_json( @@ -76,7 +67,7 @@ class DateTest(unittest.TestCase): self.assertEqual( DateTest.date.to_json( DateTest.date.from_json("2012-12-31T23:00:01-01:00")), - "2013-01-01T00:00:01Z") + "2012-12-31T23:00:01-01:00") class StringyIntegerTest(unittest.TestCase): diff --git a/common/lib/xmodule/xmodule/tests/test_import.py b/common/lib/xmodule/xmodule/tests/test_import.py index bb0d200bb6..677dd4d80e 100644 --- a/common/lib/xmodule/xmodule/tests/test_import.py +++ b/common/lib/xmodule/xmodule/tests/test_import.py @@ -13,6 +13,8 @@ from xmodule.modulestore.inheritance import compute_inherited_metadata from xmodule.fields import Date from .test_export import DATA_DIR +import datetime +from django.utils.timezone import UTC ORG = 'test_org' COURSE = 'test_course' @@ -40,7 +42,7 @@ class DummySystem(ImportSystem): load_error_modules=load_error_modules, ) - def render_template(self, template, context): + def render_template(self, _template, _context): raise Exception("Shouldn't be called") @@ -62,6 +64,7 @@ class BaseCourseTestCase(unittest.TestCase): class ImportTestCase(BaseCourseTestCase): + date = Date() def test_fallback(self): '''Check that malformed xml loads as an ErrorDescriptor.''' @@ -145,15 +148,18 @@ class ImportTestCase(BaseCourseTestCase): descriptor = system.process_xml(start_xml) compute_inherited_metadata(descriptor) + # pylint: disable=W0212 print(descriptor, descriptor._model_data) - self.assertEqual(descriptor.lms.due, Date().from_json(v)) + self.assertEqual(descriptor.lms.due, ImportTestCase.date.from_json(v)) # Check that the child inherits due correctly child = descriptor.get_children()[0] - self.assertEqual(child.lms.due, Date().from_json(v)) + self.assertEqual(child.lms.due, ImportTestCase.date.from_json(v)) self.assertEqual(child._inheritable_metadata, child._inherited_metadata) self.assertEqual(2, len(child._inherited_metadata)) - self.assertEqual('1970-01-01T00:00:00Z', child._inherited_metadata['start']) + self.assertLessEqual(ImportTestCase.date.from_json( + child._inherited_metadata['start']), + datetime.datetime.now(UTC())) self.assertEqual(v, child._inherited_metadata['due']) # Now export and check things @@ -209,9 +215,13 @@ class ImportTestCase(BaseCourseTestCase): # Check that the child does not inherit a value for due child = descriptor.get_children()[0] self.assertEqual(child.lms.due, None) + # pylint: disable=W0212 self.assertEqual(child._inheritable_metadata, child._inherited_metadata) self.assertEqual(1, len(child._inherited_metadata)) - self.assertEqual('1970-01-01T00:00:00Z', child._inherited_metadata['start']) + # why do these tests look in the internal structure v just calling child.start? + self.assertLessEqual( + ImportTestCase.date.from_json(child._inherited_metadata['start']), + datetime.datetime.now(UTC())) def test_metadata_override_default(self): """ @@ -230,14 +240,17 @@ class ImportTestCase(BaseCourseTestCase): '''.format(due=course_due, org=ORG, course=COURSE, url_name=url_name) descriptor = system.process_xml(start_xml) child = descriptor.get_children()[0] + # pylint: disable=W0212 child._model_data['due'] = child_due compute_inherited_metadata(descriptor) - self.assertEqual(descriptor.lms.due, Date().from_json(course_due)) - self.assertEqual(child.lms.due, Date().from_json(child_due)) + self.assertEqual(descriptor.lms.due, ImportTestCase.date.from_json(course_due)) + self.assertEqual(child.lms.due, ImportTestCase.date.from_json(child_due)) # Test inherited metadata. Due does not appear here (because explicitly set on child). self.assertEqual(1, len(child._inherited_metadata)) - self.assertEqual('1970-01-01T00:00:00Z', child._inherited_metadata['start']) + self.assertLessEqual( + ImportTestCase.date.from_json(child._inherited_metadata['start']), + datetime.datetime.now(UTC())) # Test inheritable metadata. This has the course inheritable value for due. self.assertEqual(2, len(child._inheritable_metadata)) self.assertEqual(course_due, child._inheritable_metadata['due']) diff --git a/common/lib/xmodule/xmodule/timeinfo.py b/common/lib/xmodule/xmodule/timeinfo.py index a7743b6bee..9a63c0477d 100644 --- a/common/lib/xmodule/xmodule/timeinfo.py +++ b/common/lib/xmodule/xmodule/timeinfo.py @@ -1,5 +1,4 @@ from .timeparse import parse_timedelta -from xmodule.util.date_utils import time_to_datetime import logging log = logging.getLogger(__name__) @@ -17,7 +16,7 @@ class TimeInfo(object): """ def __init__(self, due_date, grace_period_string): if due_date is not None: - self.display_due_date = time_to_datetime(due_date) + self.display_due_date = due_date else: self.display_due_date = None diff --git a/common/lib/xmodule/xmodule/timeparse.py b/common/lib/xmodule/xmodule/timeparse.py index 15a8233ccb..b189262761 100644 --- a/common/lib/xmodule/xmodule/timeparse.py +++ b/common/lib/xmodule/xmodule/timeparse.py @@ -1,9 +1,8 @@ """ Helper functions for handling time in the format we like. """ -import time import re -from datetime import timedelta +from datetime import timedelta, datetime TIME_FORMAT = "%Y-%m-%dT%H:%M" @@ -17,14 +16,14 @@ def parse_time(time_str): Raises ValueError if the string is not in the right format. """ - return time.strptime(time_str, TIME_FORMAT) + return datetime.strptime(time_str, TIME_FORMAT) -def stringify_time(time_struct): +def stringify_time(dt): """ - Convert a time struct to a string + Convert a datetime struct to a string """ - return time.strftime(TIME_FORMAT, time_struct) + return dt.isoformat() def parse_timedelta(time_str): """ diff --git a/common/lib/xmodule/xmodule/util/date_utils.py b/common/lib/xmodule/xmodule/util/date_utils.py index 1e64856e8f..050d65fcf1 100644 --- a/common/lib/xmodule/xmodule/util/date_utils.py +++ b/common/lib/xmodule/xmodule/util/date_utils.py @@ -1,34 +1,20 @@ -import time -import datetime - - -def get_default_time_display(time_struct, show_timezone=True): +def get_default_time_display(dt, show_timezone=True): """ - Converts a time struct to a string representation. This is the default + Converts a datetime to a string representation. This is the default representation used in Studio and LMS. It is of the form "Apr 09, 2013 at 16:00" or "Apr 09, 2013 at 16:00 UTC", depending on the value of show_timezone. - If None is passed in for time_struct, an empty string will be returned. + If None is passed in for dt, an empty string will be returned. The default value of show_timezone is True. """ - timezone = "" if time_struct is None or not show_timezone else " UTC" - return get_time_struct_display(time_struct, "%b %d, %Y at %H:%M") + timezone - - -def get_time_struct_display(time_struct, format): - """ - Converts a time struct to a string based on the given format. - - If None is passed in, an empty string will be returned. - """ - return '' if time_struct is None else time.strftime(format, time_struct) - - -def time_to_datetime(time_struct): - """ - Convert a time struct to a datetime. - - If None is passed in, None will be returned. - """ - return datetime.datetime(*time_struct[:6]) if time_struct else None + timezone = "" + if dt is not None and show_timezone: + if dt.tzinfo is not None: + try: + timezone = dt.tzinfo.tzname(dt) + except NotImplementedError: + timezone = " UTC" + else: + timezone = " UTC" + return dt.strftime("%b %d, %Y at %H:%M") + timezone diff --git a/common/test/data/full/sequential/Administrivia_and_Circuit_Elements.xml b/common/test/data/full/sequential/Administrivia_and_Circuit_Elements.xml index 47b19f75ed..35e4704d7c 100644 --- a/common/test/data/full/sequential/Administrivia_and_Circuit_Elements.xml +++ b/common/test/data/full/sequential/Administrivia_and_Circuit_Elements.xml @@ -1,24 +1,34 @@ - - - - - - S1E4 has been removed… - - - - + + + + + + S1E4 has been removed… + + + + - - - - -

Inline content…

- -
-
+ + + + +

Inline content…

+ +
+
diff --git a/lms/djangoapps/courseware/access.py b/lms/djangoapps/courseware/access.py index ace9c0096b..07987a8edf 100644 --- a/lms/djangoapps/courseware/access.py +++ b/lms/djangoapps/courseware/access.py @@ -16,6 +16,7 @@ from xmodule.x_module import XModule, XModuleDescriptor from student.models import CourseEnrollmentAllowed from courseware.masquerade import is_masquerading_as_student +from django.utils.timezone import UTC DEBUG_ACCESS = False @@ -133,7 +134,7 @@ def _has_access_course_desc(user, course, action): (staff can always enroll) """ - now = time.gmtime() + now = datetime.now(UTC()) start = course.enrollment_start end = course.enrollment_end @@ -242,7 +243,7 @@ def _has_access_descriptor(user, descriptor, action, course_context=None): # Check start date if descriptor.lms.start is not None: - now = time.gmtime() + now = datetime.now(UTC()) effective_start = _adjust_start_date_for_beta_testers(user, descriptor) if now > effective_start: # after start date, everyone can see it @@ -365,7 +366,7 @@ def _course_org_staff_group_name(location, course_context=None): def group_names_for(role, location, course_context=None): - """Returns the group names for a given role with this location. Plural + """Returns the group names for a given role with this location. Plural because it will return both the name we expect now as well as the legacy group name we support for backwards compatibility. This should not check the DB for existence of a group (like some of its callers do) because that's @@ -483,8 +484,7 @@ def _adjust_start_date_for_beta_testers(user, descriptor): non-None start date. Returns: - A time, in the same format as returned by time.gmtime(). Either the same as - start, or earlier for beta testers. + A datetime. Either the same as start, or earlier for beta testers. NOTE: number of days to adjust should be cached to avoid looking it up thousands of times per query. @@ -505,15 +505,11 @@ def _adjust_start_date_for_beta_testers(user, descriptor): beta_group = course_beta_test_group_name(descriptor.location) if beta_group in user_groups: debug("Adjust start time: user in group %s", beta_group) - # time_structs don't support subtraction, so convert to datetimes, - # subtract, convert back. - # (fun fact: datetime(*a_time_struct[:6]) is the beautiful syntax for - # converting time_structs into datetimes) - start_as_datetime = datetime(*descriptor.lms.start[:6]) + start_as_datetime = descriptor.lms.start delta = timedelta(descriptor.lms.days_early_for_beta) effective = start_as_datetime - delta # ...and back to time_struct - return effective.timetuple() + return effective return descriptor.lms.start @@ -564,7 +560,7 @@ def _has_access_to_location(user, location, access_level, course_context): return True debug("Deny: user not in groups %s", staff_groups) - if access_level == 'instructor' or access_level == 'staff': # instructors get staff privileges + if access_level == 'instructor' or access_level == 'staff': # instructors get staff privileges instructor_groups = group_names_for_instructor(location, course_context) + \ [_course_org_instructor_group_name(location, course_context)] for instructor_group in instructor_groups: diff --git a/lms/djangoapps/courseware/tests/test_access.py b/lms/djangoapps/courseware/tests/test_access.py index c1bb9f203e..34d064971f 100644 --- a/lms/djangoapps/courseware/tests/test_access.py +++ b/lms/djangoapps/courseware/tests/test_access.py @@ -1,18 +1,12 @@ -import unittest -import logging -import time -from mock import Mock, MagicMock, patch +from mock import Mock, patch -from django.conf import settings from django.test import TestCase -from xmodule.course_module import CourseDescriptor -from xmodule.error_module import ErrorDescriptor from xmodule.modulestore import Location -from xmodule.timeparse import parse_time -from xmodule.x_module import XModule, XModuleDescriptor import courseware.access as access from .factories import CourseEnrollmentAllowedFactory +import datetime +from django.utils.timezone import UTC class AccessTestCase(TestCase): @@ -77,7 +71,7 @@ class AccessTestCase(TestCase): # TODO: override DISABLE_START_DATES and test the start date branch of the method u = Mock() d = Mock() - d.start = time.gmtime(time.time() - 86400) # make sure the start time is in the past + d.start = datetime.datetime.now(UTC()) - datetime.timedelta(days=1) # make sure the start time is in the past # Always returns true because DISABLE_START_DATES is set in test.py self.assertTrue(access._has_access_descriptor(u, d, 'load')) @@ -85,8 +79,8 @@ class AccessTestCase(TestCase): def test__has_access_course_desc_can_enroll(self): u = Mock() - yesterday = time.gmtime(time.time() - 86400) - tomorrow = time.gmtime(time.time() + 86400) + yesterday = datetime.datetime.now(UTC()) - datetime.timedelta(days=1) + tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1) c = Mock(enrollment_start=yesterday, enrollment_end=tomorrow) # User can enroll if it is between the start and end dates diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py index ec3e55b1b8..f037fc6c3e 100644 --- a/lms/djangoapps/courseware/tests/tests.py +++ b/lms/djangoapps/courseware/tests/tests.py @@ -3,7 +3,6 @@ Test for lms courseware app ''' import logging import json -import time import random from urlparse import urlsplit, urlunsplit @@ -30,6 +29,8 @@ from xmodule.modulestore.django import modulestore from xmodule.modulestore import Location from xmodule.modulestore.xml_importer import import_from_xml from xmodule.modulestore.xml import XMLModuleStore +import datetime +from django.utils.timezone import UTC log = logging.getLogger("mitx." + __name__) @@ -603,9 +604,9 @@ class TestViewAuth(LoginEnrollmentTestCase): """Actually do the test, relying on settings to be right.""" # Make courses start in the future - tomorrow = time.time() + 24 * 3600 - self.toy.lms.start = time.gmtime(tomorrow) - self.full.lms.start = time.gmtime(tomorrow) + tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1) + self.toy.lms.start = tomorrow + self.full.lms.start = tomorrow self.assertFalse(self.toy.has_started()) self.assertFalse(self.full.has_started()) @@ -728,18 +729,18 @@ class TestViewAuth(LoginEnrollmentTestCase): """Actually do the test, relying on settings to be right.""" # Make courses start in the future - tomorrow = time.time() + 24 * 3600 - nextday = tomorrow + 24 * 3600 - yesterday = time.time() - 24 * 3600 + tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1) + nextday = tomorrow + datetime.timedelta(days=1) + yesterday = datetime.datetime.now(UTC()) - datetime.timedelta(days=1) print "changing" # toy course's enrollment period hasn't started - self.toy.enrollment_start = time.gmtime(tomorrow) - self.toy.enrollment_end = time.gmtime(nextday) + self.toy.enrollment_start = tomorrow + self.toy.enrollment_end = nextday # full course's has - self.full.enrollment_start = time.gmtime(yesterday) - self.full.enrollment_end = time.gmtime(tomorrow) + self.full.enrollment_start = yesterday + self.full.enrollment_end = tomorrow print "login" # First, try with an enrolled student @@ -778,12 +779,10 @@ class TestViewAuth(LoginEnrollmentTestCase): self.assertFalse(settings.MITX_FEATURES['DISABLE_START_DATES']) # Make courses start in the future - tomorrow = time.time() + 24 * 3600 - # nextday = tomorrow + 24 * 3600 - # yesterday = time.time() - 24 * 3600 + tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1) # toy course's hasn't started - self.toy.lms.start = time.gmtime(tomorrow) + self.toy.lms.start = tomorrow self.assertFalse(self.toy.has_started()) # but should be accessible for beta testers @@ -854,7 +853,7 @@ class TestSubmittingProblems(LoginEnrollmentTestCase): modx_url = self.modx_url(problem_location, 'problem_check') answer_key_prefix = 'input_i4x-edX-{}-problem-{}_'.format(self.course_slug, problem_url_name) resp = self.client.post(modx_url, - { (answer_key_prefix + k): v for k,v in responses.items() } + { (answer_key_prefix + k): v for k, v in responses.items() } ) return resp @@ -925,7 +924,7 @@ class TestCourseGrader(TestSubmittingProblems): # Only get half of the first problem correct self.submit_question_answer('H1P1', {'2_1': 'Correct', '2_2': 'Incorrect'}) self.check_grade_percent(0.06) - self.assertEqual(earned_hw_scores(), [1.0, 0, 0]) # Order matters + self.assertEqual(earned_hw_scores(), [1.0, 0, 0]) # Order matters self.assertEqual(score_for_hw('Homework1'), [1.0, 0.0]) # Get both parts of the first problem correct @@ -958,16 +957,16 @@ class TestCourseGrader(TestSubmittingProblems): # Third homework self.submit_question_answer('H3P1', {'2_1': 'Correct', '2_2': 'Correct'}) - self.check_grade_percent(0.42) # Score didn't change + self.check_grade_percent(0.42) # Score didn't change self.assertEqual(earned_hw_scores(), [4.0, 4.0, 2.0]) self.submit_question_answer('H3P2', {'2_1': 'Correct', '2_2': 'Correct'}) - self.check_grade_percent(0.5) # Now homework2 dropped. Score changes + self.check_grade_percent(0.5) # Now homework2 dropped. Score changes self.assertEqual(earned_hw_scores(), [4.0, 4.0, 4.0]) # Now we answer the final question (worth half of the grade) self.submit_question_answer('FinalQuestion', {'2_1': 'Correct', '2_2': 'Correct'}) - self.check_grade_percent(1.0) # Hooray! We got 100% + self.check_grade_percent(1.0) # Hooray! We got 100% @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) @@ -1000,7 +999,7 @@ class TestSchematicResponse(TestSubmittingProblems): { '2_1': json.dumps( [['transient', {'Z': [ [0.0000004, 2.8], - [0.0000009, 0.0], # wrong. + [0.0000009, 0.0], # wrong. [0.0000014, 2.8], [0.0000019, 2.8], [0.0000024, 2.8], diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py index 276956f0e9..007a8fedfd 100644 --- a/lms/djangoapps/django_comment_client/utils.py +++ b/lms/djangoapps/django_comment_client/utils.py @@ -1,14 +1,9 @@ -import time from collections import defaultdict import logging -import time import urllib from datetime import datetime from courseware.module_render import get_module -from xmodule.modulestore import Location -from xmodule.modulestore.django import modulestore -from xmodule.modulestore.search import path_to_location from django.contrib.auth.models import User from django.core.urlresolvers import reverse from django.db import connection @@ -16,13 +11,12 @@ from django.http import HttpResponse from django.utils import simplejson from django_comment_common.models import Role from django_comment_client.permissions import check_permissions_by_view -from xmodule.modulestore.exceptions import NoPathToItem from mitxmako import middleware import pystache_custom as pystache -from xmodule.modulestore import Location from xmodule.modulestore.django import modulestore +from django.utils.timezone import UTC log = logging.getLogger(__name__) @@ -100,7 +94,7 @@ def get_discussion_category_map(course): def filter_unstarted_categories(category_map): - now = time.gmtime() + now = datetime.now(UTC()) result_map = {} @@ -220,7 +214,7 @@ def initialize_discussion_info(course): for topic, entry in course.discussion_topics.items(): category_map['entries'][topic] = {"id": entry["id"], "sort_key": entry.get("sort_key", topic), - "start_date": time.gmtime()} + "start_date": datetime.now(UTC())} sort_map_entries(category_map) _DISCUSSIONINFO[course.id]['id_map'] = discussion_id_map @@ -292,7 +286,7 @@ def get_ability(course_id, content, user): 'can_vote': check_permissions_by_view(user, course_id, content, "vote_for_thread" if content['type'] == 'thread' else "vote_for_comment"), } -#TODO: RENAME +# TODO: RENAME def get_annotated_content_info(course_id, content, user, user_info): @@ -310,7 +304,7 @@ def get_annotated_content_info(course_id, content, user, user_info): 'ability': get_ability(course_id, content, user), } -#TODO: RENAME +# TODO: RENAME def get_annotated_content_infos(course_id, thread, user, user_info):