Commit d7194e6b by Don Mitchell

struct_time to datetime conversion.

parent 673c015e
...@@ -271,7 +271,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -271,7 +271,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
) )
self.assertTrue(getattr(draft_problem, 'is_draft', False)) self.assertTrue(getattr(draft_problem, 'is_draft', False))
#now requery with depth # now requery with depth
course = modulestore('draft').get_item( course = modulestore('draft').get_item(
Location(['i4x', 'edX', 'simple', 'course', '2012_Fall', None]), Location(['i4x', 'edX', 'simple', 'course', '2012_Fall', None]),
depth=None depth=None
...@@ -539,7 +539,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -539,7 +539,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
on_disk = loads(grading_policy.read()) on_disk = loads(grading_policy.read())
self.assertEqual(on_disk, course.grading_policy) self.assertEqual(on_disk, course.grading_policy)
#check for policy.json # check for policy.json
self.assertTrue(filesystem.exists('policy.json')) self.assertTrue(filesystem.exists('policy.json'))
# compare what's on disk to what we have in the course module # compare what's on disk to what we have in the course module
...@@ -990,7 +990,7 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -990,7 +990,7 @@ class ContentStoreTest(ModuleStoreTestCase):
def test_metadata_inheritance(self): def test_metadata_inheritance(self):
module_store = modulestore('direct') 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])) course = module_store.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None]))
......
...@@ -151,22 +151,16 @@ class CourseDetailsViewTest(CourseTestCase): ...@@ -151,22 +151,16 @@ class CourseDetailsViewTest(CourseTestCase):
self.assertEqual(details['intro_video'], encoded.get('intro_video', None), context + " intro_video not ==") self.assertEqual(details['intro_video'], encoded.get('intro_video', None), context + " intro_video not ==")
self.assertEqual(details['effort'], encoded['effort'], context + " efforts 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): def compare_date_fields(self, details, encoded, context, field):
if details[field] is not None: if details[field] is not None:
date = Date() date = Date()
if field in encoded and encoded[field] is not None: if field in encoded and encoded[field] is not None:
encoded_encoded = date.from_json(encoded[field]) dt1 = date.from_json(encoded[field])
dt1 = CourseDetailsViewTest.struct_to_datetime(encoded_encoded)
if isinstance(details[field], datetime.datetime): if isinstance(details[field], datetime.datetime):
dt2 = details[field] dt2 = details[field]
else: else:
details_encoded = date.from_json(details[field]) dt2 = date.from_json(details[field])
dt2 = CourseDetailsViewTest.struct_to_datetime(details_encoded)
expected_delta = datetime.timedelta(0) expected_delta = datetime.timedelta(0)
self.assertEqual(dt1 - dt2, expected_delta, str(dt1) + "!=" + str(dt2) + " at " + context) self.assertEqual(dt1 - dt2, expected_delta, str(dt1) + "!=" + str(dt2) + " at " + context)
......
...@@ -62,7 +62,7 @@ def asset_index(request, org, course, name): ...@@ -62,7 +62,7 @@ def asset_index(request, org, course, name):
asset_id = asset['_id'] asset_id = asset['_id']
display_info = {} display_info = {}
display_info['displayname'] = asset['displayname'] 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']) 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) display_info['url'] = StaticContent.get_url_path_from_location(asset_location)
...@@ -131,7 +131,7 @@ def upload_asset(request, org, course, coursename): ...@@ -131,7 +131,7 @@ def upload_asset(request, org, course, coursename):
readback = contentstore().find(content.location) readback = contentstore().find(content.location)
response_payload = {'displayname': content.name, 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), '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, 'thumb_url': StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_content is not None else None,
'msg': 'Upload completed' 'msg': 'Upload completed'
...@@ -231,7 +231,7 @@ def generate_export_course(request, org, course, name): ...@@ -231,7 +231,7 @@ def generate_export_course(request, org, course, name):
logging.debug('root = {0}'.format(root_dir)) logging.debug('root = {0}'.format(root_dir))
export_to_xml(modulestore('direct'), contentstore(), loc, root_dir, name, modulestore()) 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)) logging.debug('tar file being generated at {0}'.format(export_file.name))
tar_file = tarfile.open(name=export_file.name, mode='w:gz') tar_file = tarfile.open(name=export_file.name, mode='w:gz')
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
Views related to operations on course objects Views related to operations on course objects
""" """
import json import json
import time
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django_future.csrf import ensure_csrf_cookie from django_future.csrf import ensure_csrf_cookie
...@@ -32,6 +31,8 @@ from .component import OPEN_ENDED_COMPONENT_TYPES, \ ...@@ -32,6 +31,8 @@ from .component import OPEN_ENDED_COMPONENT_TYPES, \
NOTE_COMPONENT_TYPES, ADVANCED_COMPONENT_POLICY_KEY NOTE_COMPONENT_TYPES, ADVANCED_COMPONENT_POLICY_KEY
from django_comment_common.utils import seed_permissions_roles from django_comment_common.utils import seed_permissions_roles
import datetime
from django.utils.timezone import UTC
# TODO: should explicitly enumerate exports with __all__ # TODO: should explicitly enumerate exports with __all__
...@@ -130,7 +131,7 @@ def create_new_course(request): ...@@ -130,7 +131,7 @@ def create_new_course(request):
new_course.display_name = display_name new_course.display_name = display_name
# set a default start date to now # set a default start date to now
new_course.start = time.gmtime() new_course.start = datetime.datetime.now(UTC())
initialize_course_tabs(new_course) initialize_course_tabs(new_course)
...@@ -357,49 +358,49 @@ def course_advanced_updates(request, org, course, name): ...@@ -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 # Whether or not to filter the tabs key out of the settings metadata
filter_tabs = True filter_tabs = True
#Check to see if the user instantiated any advanced components. This is a hack # Check to see if the user instantiated any advanced components. This is a hack
#that does the following : # that does the following :
# 1) adds/removes the open ended panel tab to a course automatically if the user # 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 # 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 # 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 # indicated that they want the notes module enabled in their course
# TODO refactor the above into distinct advanced policy settings # TODO refactor the above into distinct advanced policy settings
if ADVANCED_COMPONENT_POLICY_KEY in request_body: 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) course_module = modulestore().get_item(location)
#Maps tab types to components # Maps tab types to components
tab_component_map = { tab_component_map = {
'open_ended': OPEN_ENDED_COMPONENT_TYPES, 'open_ended': OPEN_ENDED_COMPONENT_TYPES,
'notes': NOTE_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(): for tab_type in tab_component_map.keys():
component_types = tab_component_map.get(tab_type) component_types = tab_component_map.get(tab_type)
found_ac_type = False found_ac_type = False
for ac_type in component_types: for ac_type in component_types:
if ac_type in request_body[ADVANCED_COMPONENT_POLICY_KEY]: 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) 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: if changed:
course_module.tabs = new_tabs course_module.tabs = new_tabs
request_body.update({'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 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 found_ac_type = True
break 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. # we may need to remove the tab from the course.
if not found_ac_type: 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) changed, new_tabs = remove_extra_panel_tab(tab_type, course_module)
if changed: if changed:
course_module.tabs = new_tabs course_module.tabs = new_tabs
request_body.update({'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 filter_tabs = False
response_json = json.dumps(CourseMetadata.update_from_json(location, response_json = json.dumps(CourseMetadata.update_from_json(location,
......
...@@ -3,26 +3,26 @@ from xmodule.modulestore.exceptions import ItemNotFoundError ...@@ -3,26 +3,26 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore.inheritance import own_metadata from xmodule.modulestore.inheritance import own_metadata
import json import json
from json.encoder import JSONEncoder from json.encoder import JSONEncoder
import time
from contentstore.utils import get_modulestore from contentstore.utils import get_modulestore
from models.settings import course_grading from models.settings import course_grading
from contentstore.utils import update_item from contentstore.utils import update_item
from xmodule.fields import Date from xmodule.fields import Date
import re import re
import logging import logging
import datetime
class CourseDetails(object): class CourseDetails(object):
def __init__(self, location): def __init__(self, location):
self.course_location = location # a Location obj self.course_location = location # a Location obj
self.start_date = None # 'start' self.start_date = None # 'start'
self.end_date = None # 'end' self.end_date = None # 'end'
self.enrollment_start = None self.enrollment_start = None
self.enrollment_end = None self.enrollment_end = None
self.syllabus = None # a pdf file asset self.syllabus = None # a pdf file asset
self.overview = "" # html to render as the overview self.overview = "" # html to render as the overview
self.intro_video = None # a video pointer self.intro_video = None # a video pointer
self.effort = None # int hours/week self.effort = None # int hours/week
@classmethod @classmethod
def fetch(cls, course_location): def fetch(cls, course_location):
...@@ -73,9 +73,9 @@ class CourseDetails(object): ...@@ -73,9 +73,9 @@ class CourseDetails(object):
""" """
Decode the json into CourseDetails and save any changed attrs to the db 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'] 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) descriptor = get_modulestore(course_location).get_item(course_location)
dirty = False dirty = False
...@@ -181,7 +181,7 @@ class CourseSettingsEncoder(json.JSONEncoder): ...@@ -181,7 +181,7 @@ class CourseSettingsEncoder(json.JSONEncoder):
return obj.__dict__ return obj.__dict__
elif isinstance(obj, Location): elif isinstance(obj, Location):
return obj.dict() return obj.dict()
elif isinstance(obj, time.struct_time): elif isinstance(obj, datetime.datetime):
return Date().to_json(obj) return Date().to_json(obj)
else: else:
return JSONEncoder.default(self, obj) return JSONEncoder.default(self, obj)
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! <%!
import logging 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 %> <%! from django.core.urlresolvers import reverse %>
...@@ -36,11 +36,15 @@ ...@@ -36,11 +36,15 @@
<div class="datepair" data-language="javascript"> <div class="datepair" data-language="javascript">
<div class="field field-start-date"> <div class="field field-start-date">
<label for="start_date">Release Day</label> <label for="start_date">Release Day</label>
<input type="text" id="start_date" name="start_date" value="${get_time_struct_display(subsection.lms.start, '%m/%d/%Y')}" placeholder="MM/DD/YYYY" class="date" size='15' autocomplete="off"/> <input type="text" id="start_date" name="start_date"
value="${subsection.lms.start.strftime('%m/%d/%Y')}"
placeholder="MM/DD/YYYY" class="date" size='15' autocomplete="off"/>
</div> </div>
<div class="field field-start-time"> <div class="field field-start-time">
<label for="start_time">Release Time (<abbr title="Coordinated Universal Time">UTC</abbr>)</label> <label for="start_time">Release Time (<abbr title="Coordinated Universal Time">UTC</abbr>)</label>
<input type="text" id="start_time" name="start_time" value="${get_time_struct_display(subsection.lms.start, '%H:%M')}" placeholder="HH:MM" class="time" size='10' autocomplete="off"/> <input type="text" id="start_time" name="start_time"
value="${subsection.lms.start.strftime('%H:%M')}"
placeholder="HH:MM" class="time" size='10' autocomplete="off"/>
</div> </div>
</div> </div>
% if subsection.lms.start != parent_item.lms.start and subsection.lms.start: % if subsection.lms.start != parent_item.lms.start and subsection.lms.start:
...@@ -48,7 +52,7 @@ ...@@ -48,7 +52,7 @@
<p class="notice">The date above differs from the release date of ${parent_item.display_name_with_default}, which is unset. <p class="notice">The date above differs from the release date of ${parent_item.display_name_with_default}, which is unset.
% else: % else:
<p class="notice">The date above differs from the release date of ${parent_item.display_name_with_default} – <p class="notice">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 % endif
<a href="#" class="sync-date no-spinner">Sync to ${parent_item.display_name_with_default}.</a></p> <a href="#" class="sync-date no-spinner">Sync to ${parent_item.display_name_with_default}.</a></p>
% endif % endif
...@@ -65,11 +69,15 @@ ...@@ -65,11 +69,15 @@
<div class="datepair date-setter"> <div class="datepair date-setter">
<div class="field field-start-date"> <div class="field field-start-date">
<label for="due_date">Due Day</label> <label for="due_date">Due Day</label>
<input type="text" id="due_date" name="due_date" value="${get_time_struct_display(subsection.lms.due, '%m/%d/%Y')}" placeholder="MM/DD/YYYY" class="date" size='15' autocomplete="off"/> <input type="text" id="due_date" name="due_date"
value="${subsection.lms.due.strftime('%m/%d/%Y') if subsection.lms.due else ''}"
placeholder="MM/DD/YYYY" class="date" size='15' autocomplete="off"/>
</div> </div>
<div class="field field-start-time"> <div class="field field-start-time">
<label for="due_time">Due Time (<abbr title="Coordinated Universal Time">UTC</abbr>)</label> <label for="due_time">Due Time (<abbr title="Coordinated Universal Time">UTC</abbr>)</label>
<input type="text" id="due_time" name="due_time" value="${get_time_struct_display(subsection.lms.due, '%H:%M')}" placeholder="HH:MM" class="time" size='10' autocomplete="off"/> <input type="text" id="due_time" name="due_time"
value="${subsection.lms.due.strftime('%H:%M') if subsection.lms.due else ''}"
placeholder="HH:MM" class="time" size='10' autocomplete="off"/>
</div> </div>
<a href="#" class="remove-date">Remove due date</a> <a href="#" class="remove-date">Remove due date</a>
</div> </div>
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! <%!
import logging import logging
from xmodule.util.date_utils import get_time_struct_display from xmodule.util import date_utils
%> %>
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">Course Outline</%block> <%block name="title">Course Outline</%block>
...@@ -154,14 +154,15 @@ ...@@ -154,14 +154,15 @@
<h3 class="section-name" data-name="${section.display_name_with_default | h}"></h3> <h3 class="section-name" data-name="${section.display_name_with_default | h}"></h3>
<div class="section-published-date"> <div class="section-published-date">
<% <%
start_date_str = get_time_struct_display(section.lms.start, '%m/%d/%Y') start_date_str = section.lms.start.strftime('%m/%d/%Y')
start_time_str = get_time_struct_display(section.lms.start, '%H:%M') start_time_str = section.lms.start.strftime('%H:%M')
%> %>
%if section.lms.start is None: %if section.lms.start is None:
<span class="published-status">This section has not been released.</span> <span class="published-status">This section has not been released.</span>
<a href="#" class="schedule-button" data-date="" data-time="" data-id="${section.location}">Schedule</a> <a href="#" class="schedule-button" data-date="" data-time="" data-id="${section.location}">Schedule</a>
%else: %else:
<span class="published-status"><strong>Will Release:</strong> ${get_time_struct_display(section.lms.start, '%m/%d/%Y at %H:%M UTC')}</span> <span class="published-status"><strong>Will Release:</strong>
${date_utils.get_default_time_display(section.lms.start)}</span>
<a href="#" class="edit-button" data-date="${start_date_str}" data-time="${start_time_str}" data-id="${section.location}">Edit</a> <a href="#" class="edit-button" data-date="${start_date_str}" data-time="${start_time_str}" data-id="${section.location}">Edit</a>
%endif %endif
</div> </div>
......
import logging from django.http import HttpResponse, HttpResponseNotModified
import time
from django.http import HttpResponse, Http404, HttpResponseNotModified
from xmodule.contentstore.django import contentstore from xmodule.contentstore.django import contentstore
from xmodule.contentstore.content import StaticContent, XASSET_LOCATION_TAG from xmodule.contentstore.content import StaticContent, XASSET_LOCATION_TAG
...@@ -20,7 +17,7 @@ class StaticContentServer(object): ...@@ -20,7 +17,7 @@ class StaticContentServer(object):
# return a 'Bad Request' to browser as we have a malformed Location # return a 'Bad Request' to browser as we have a malformed Location
response = HttpResponse() response = HttpResponse()
response.status_code = 400 response.status_code = 400
return response return response
# first look in our cache so we don't have to round-trip to the DB # first look in our cache so we don't have to round-trip to the DB
content = get_cached_content(loc) content = get_cached_content(loc)
......
from optparse import make_option from optparse import make_option
from time import strftime
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
...@@ -128,8 +127,8 @@ class Command(BaseCommand): ...@@ -128,8 +127,8 @@ class Command(BaseCommand):
exam = CourseDescriptor.TestCenterExam(course_id, exam_name, exam_info) exam = CourseDescriptor.TestCenterExam(course_id, exam_name, exam_info)
# update option values for date_first and date_last to use YYYY-MM-DD format # update option values for date_first and date_last to use YYYY-MM-DD format
# instead of YYYY-MM-DDTHH:MM # 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_first'] = exam.first_eligible_appointment_date.strftime("%Y-%m-%d")
our_options['eligibility_appointment_date_last'] = strftime("%Y-%m-%d", exam.last_eligible_appointment_date) our_options['eligibility_appointment_date_last'] = exam.last_eligible_appointment_date.strftime("%Y-%m-%d")
if exam is None: if exam is None:
raise CommandError("Exam for course_id {} does not exist".format(course_id)) raise CommandError("Exam for course_id {} does not exist".format(course_id))
......
...@@ -16,7 +16,6 @@ import json ...@@ -16,7 +16,6 @@ import json
import logging import logging
import uuid import uuid
from random import randint from random import randint
from time import strftime
from django.conf import settings from django.conf import settings
...@@ -54,7 +53,7 @@ class UserProfile(models.Model): ...@@ -54,7 +53,7 @@ class UserProfile(models.Model):
class Meta: class Meta:
db_table = "auth_userprofile" db_table = "auth_userprofile"
## CRITICAL TODO/SECURITY # # CRITICAL TODO/SECURITY
# Sanitize all fields. # Sanitize all fields.
# This is not visible to other users, but could introduce holes later # This is not visible to other users, but could introduce holes later
user = models.OneToOneField(User, unique=True, db_index=True, related_name='profile') user = models.OneToOneField(User, unique=True, db_index=True, related_name='profile')
...@@ -429,8 +428,8 @@ class TestCenterRegistration(models.Model): ...@@ -429,8 +428,8 @@ class TestCenterRegistration(models.Model):
registration.course_id = exam.course_id registration.course_id = exam.course_id
registration.accommodation_request = accommodation_request.strip() registration.accommodation_request = accommodation_request.strip()
registration.exam_series_code = exam.exam_series_code 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_first = exam.first_eligible_appointment_date.strftime("%Y-%m-%d")
registration.eligibility_appointment_date_last = strftime("%Y-%m-%d", exam.last_eligible_appointment_date) registration.eligibility_appointment_date_last = exam.last_eligible_appointment_date.strftime("%Y-%m-%d")
registration.client_authorization_id = cls._create_client_authorization_id() registration.client_authorization_id = cls._create_client_authorization_id()
# accommodation_code remains blank for now, along with Pearson confirmation information # accommodation_code remains blank for now, along with Pearson confirmation information
return registration return registration
...@@ -598,7 +597,7 @@ def unique_id_for_user(user): ...@@ -598,7 +597,7 @@ def unique_id_for_user(user):
return h.hexdigest() 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 # Given an optional field for type of group
class UserTestGroup(models.Model): class UserTestGroup(models.Model):
users = models.ManyToManyField(User, db_index=True) users = models.ManyToManyField(User, db_index=True)
...@@ -626,7 +625,7 @@ class Registration(models.Model): ...@@ -626,7 +625,7 @@ class Registration(models.Model):
def activate(self): def activate(self):
self.user.is_active = True self.user.is_active = True
self.user.save() self.user.save()
#self.delete() # self.delete()
class PendingNameChange(models.Model): class PendingNameChange(models.Model):
...@@ -648,7 +647,7 @@ class CourseEnrollment(models.Model): ...@@ -648,7 +647,7 @@ class CourseEnrollment(models.Model):
created = models.DateTimeField(auto_now_add=True, null=True, db_index=True) created = models.DateTimeField(auto_now_add=True, null=True, db_index=True)
class Meta: class Meta:
unique_together = (('user', 'course_id'), ) unique_together = (('user', 'course_id'),)
def __unicode__(self): def __unicode__(self):
return "[CourseEnrollment] %s: %s (%s)" % (self.user, self.course_id, self.created) return "[CourseEnrollment] %s: %s (%s)" % (self.user, self.course_id, self.created)
...@@ -667,12 +666,12 @@ class CourseEnrollmentAllowed(models.Model): ...@@ -667,12 +666,12 @@ class CourseEnrollmentAllowed(models.Model):
created = models.DateTimeField(auto_now_add=True, null=True, db_index=True) created = models.DateTimeField(auto_now_add=True, null=True, db_index=True)
class Meta: class Meta:
unique_together = (('email', 'course_id'), ) unique_together = (('email', 'course_id'),)
def __unicode__(self): def __unicode__(self):
return "[CourseEnrollmentAllowed] %s: %s (%s)" % (self.email, self.course_id, self.created) 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. #### Helper methods for use from python manage.py shell and other classes.
......
import re import re
import json import json
import logging import logging
import time
import static_replace import static_replace
from django.conf import settings from django.conf import settings
...@@ -9,6 +8,8 @@ from functools import wraps ...@@ -9,6 +8,8 @@ from functools import wraps
from mitxmako.shortcuts import render_to_string from mitxmako.shortcuts import render_to_string
from xmodule.seq_module import SequenceModule from xmodule.seq_module import SequenceModule
from xmodule.vertical_module import VerticalModule from xmodule.vertical_module import VerticalModule
import datetime
from django.utils.timezone import UTC
log = logging.getLogger("mitx.xmodule_modifiers") log = logging.getLogger("mitx.xmodule_modifiers")
...@@ -83,7 +84,7 @@ def grade_histogram(module_id): ...@@ -83,7 +84,7 @@ def grade_histogram(module_id):
cursor.execute(q, [module_id]) cursor.execute(q, [module_id])
grades = list(cursor.fetchall()) 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: if len(grades) >= 1 and grades[0][0] is None:
return [] return []
return grades return grades
...@@ -101,7 +102,7 @@ def add_histogram(get_html, module, user): ...@@ -101,7 +102,7 @@ def add_histogram(get_html, module, user):
@wraps(get_html) @wraps(get_html)
def _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() return get_html()
module_id = module.id module_id = module.id
...@@ -132,7 +133,7 @@ def add_histogram(get_html, module, user): ...@@ -132,7 +133,7 @@ def add_histogram(get_html, module, user):
# useful to indicate to staff if problem has been released or not # 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 # 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" is_released = "unknown"
mstart = module.descriptor.lms.start mstart = module.descriptor.lms.start
......
...@@ -144,11 +144,11 @@ class InputTypeBase(object): ...@@ -144,11 +144,11 @@ class InputTypeBase(object):
self.tag = xml.tag self.tag = xml.tag
self.system = system self.system = system
## NOTE: ID should only come from one place. If it comes from multiple, # # 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 # # 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, # # 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 # # we can swap this around in the future if there's a more logical
## order. # # order.
self.input_id = state.get('id', xml.get('id')) self.input_id = state.get('id', xml.get('id'))
if self.input_id is None: if self.input_id is None:
...@@ -769,7 +769,7 @@ class MatlabInput(CodeInput): ...@@ -769,7 +769,7 @@ class MatlabInput(CodeInput):
# construct xqueue headers # construct xqueue headers
qinterface = self.system.xqueue['interface'] 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') callback_url = self.system.xqueue['construct_callback']('ungraded_response')
anonymous_student_id = self.system.anonymous_student_id anonymous_student_id = self.system.anonymous_student_id
queuekey = xqueue_interface.make_hashkey(str(self.system.seed) + qtime + queuekey = xqueue_interface.make_hashkey(str(self.system.seed) + qtime +
......
...@@ -11,7 +11,7 @@ import sys ...@@ -11,7 +11,7 @@ import sys
from pkg_resources import resource_string from pkg_resources import resource_string
from capa.capa_problem import LoncapaProblem from capa.capa_problem import LoncapaProblem
from capa.responsetypes import StudentInputError,\ from capa.responsetypes import StudentInputError, \
ResponseError, LoncapaProblemError ResponseError, LoncapaProblemError
from capa.util import convert_files_to_filenames from capa.util import convert_files_to_filenames
from .progress import Progress from .progress import Progress
...@@ -20,7 +20,7 @@ from xmodule.raw_module import RawDescriptor ...@@ -20,7 +20,7 @@ from xmodule.raw_module import RawDescriptor
from xmodule.exceptions import NotFoundError, ProcessingError from xmodule.exceptions import NotFoundError, ProcessingError
from xblock.core import Scope, String, Boolean, Object from xblock.core import Scope, String, Boolean, Object
from .fields import Timedelta, Date, StringyInteger, StringyFloat 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") log = logging.getLogger("mitx.courseware")
...@@ -134,7 +134,7 @@ class CapaModule(CapaFields, XModule): ...@@ -134,7 +134,7 @@ class CapaModule(CapaFields, XModule):
def __init__(self, system, location, descriptor, model_data): def __init__(self, system, location, descriptor, model_data):
XModule.__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: if self.graceperiod is not None and due_date:
self.close_date = due_date + self.graceperiod self.close_date = due_date + self.graceperiod
...@@ -502,7 +502,7 @@ class CapaModule(CapaFields, XModule): ...@@ -502,7 +502,7 @@ class CapaModule(CapaFields, XModule):
Is it now past this problem's due date, including grace period? Is it now past this problem's due date, including grace period?
""" """
return (self.close_date is not None and return (self.close_date is not None and
datetime.datetime.utcnow() > self.close_date) datetime.datetime.now(UTC()) > self.close_date)
def closed(self): def closed(self):
''' Is the student still allowed to submit answers? ''' ''' Is the student still allowed to submit answers? '''
......
...@@ -4,7 +4,6 @@ from math import exp ...@@ -4,7 +4,6 @@ from math import exp
from lxml import etree from lxml import etree
from path import path # NOTE (THK): Only used for detecting presence of syllabus from path import path # NOTE (THK): Only used for detecting presence of syllabus
import requests import requests
import time
from datetime import datetime from datetime import datetime
import dateutil.parser import dateutil.parser
...@@ -14,11 +13,11 @@ from xmodule.seq_module import SequenceDescriptor, SequenceModule ...@@ -14,11 +13,11 @@ from xmodule.seq_module import SequenceDescriptor, SequenceModule
from xmodule.timeparse import parse_time from xmodule.timeparse import parse_time
from xmodule.util.decorators import lazyproperty from xmodule.util.decorators import lazyproperty
from xmodule.graders import grader_from_conf from xmodule.graders import grader_from_conf
from xmodule.util.date_utils import time_to_datetime
import json import json
from xblock.core import Scope, List, String, Object, Boolean from xblock.core import Scope, List, String, Object, Boolean
from .fields import Date from .fields import Date
from django.utils.timezone import UTC
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -219,8 +218,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): ...@@ -219,8 +218,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
msg = None msg = None
if self.start is None: if self.start is None:
msg = "Course loaded without a valid start date. id = %s" % self.id msg = "Course loaded without a valid start date. id = %s" % self.id
# hack it -- start in 1970 self.start = datetime.now(UTC())
self.start = time.gmtime(0)
log.critical(msg) log.critical(msg)
self.system.error_tracker(msg) self.system.error_tracker(msg)
...@@ -392,7 +390,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): ...@@ -392,7 +390,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
textbook_xml_object.set('book_url', textbook.book_url) textbook_xml_object.set('book_url', textbook.book_url)
xml_object.append(textbook_xml_object) xml_object.append(textbook_xml_object)
return xml_object return xml_object
def has_ended(self): def has_ended(self):
...@@ -403,10 +401,10 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): ...@@ -403,10 +401,10 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
if self.end is None: if self.end is None:
return False return False
return time.gmtime() > self.end return datetime.now(UTC()) > self.end
def has_started(self): def has_started(self):
return time.gmtime() > self.start return datetime.now(UTC()) > self.start
@property @property
def grader(self): def grader(self):
...@@ -547,14 +545,16 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): ...@@ -547,14 +545,16 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
announcement = self.announcement announcement = self.announcement
if announcement is not None: if announcement is not None:
announcement = time_to_datetime(announcement) announcement = announcement
try: try:
start = dateutil.parser.parse(self.advertised_start) start = dateutil.parser.parse(self.advertised_start)
if start.tzinfo is None:
start = start.replace(tzinfo=UTC())
except (ValueError, AttributeError): except (ValueError, AttributeError):
start = time_to_datetime(self.start) start = self.start
now = datetime.utcnow() now = datetime.now(UTC())
return announcement, start, now return announcement, start, now
...@@ -656,7 +656,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): ...@@ -656,7 +656,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
elif self.advertised_start is None and self.start is None: elif self.advertised_start is None and self.start is None:
return 'TBD' return 'TBD'
else: 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 @property
def end_date_text(self): def end_date_text(self):
...@@ -665,7 +665,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): ...@@ -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. 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 @property
def forum_posts_allowed(self): def forum_posts_allowed(self):
...@@ -673,7 +673,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): ...@@ -673,7 +673,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
blackout_periods = [(parse_time(start), parse_time(end)) blackout_periods = [(parse_time(start), parse_time(end))
for start, end for start, end
in self.discussion_blackouts] in self.discussion_blackouts]
now = time.gmtime() now = datetime.now(UTC())
for start, end in blackout_periods: for start, end in blackout_periods:
if start <= now <= end: if start <= now <= end:
return False return False
...@@ -699,7 +699,8 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): ...@@ -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 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: if self.last_eligible_appointment_date is None:
raise ValueError("Last appointment date must be specified") 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 self.registration_end_date = self._try_parse_time('Registration_End_Date') or self.last_eligible_appointment_date
# do validation within the exam info: # do validation within the exam info:
if self.registration_start_date > self.registration_end_date: if self.registration_start_date > self.registration_end_date:
...@@ -725,32 +726,32 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): ...@@ -725,32 +726,32 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
return None return None
def has_started(self): 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): 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): 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): def has_ended_registration(self):
return time.gmtime() > self.registration_end_date return datetime.now(UTC()) > self.registration_end_date
def is_registering(self): def is_registering(self):
now = time.gmtime() now = datetime.now(UTC())
return now >= self.registration_start_date and now <= self.registration_end_date return now >= self.registration_start_date and now <= self.registration_end_date
@property @property
def first_eligible_appointment_date_text(self): 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 @property
def last_eligible_appointment_date_text(self): 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 @property
def registration_end_date_text(self): 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 @property
def current_test_center_exam(self): def current_test_center_exam(self):
......
...@@ -2,19 +2,19 @@ import time ...@@ -2,19 +2,19 @@ import time
import logging import logging
import re import re
from datetime import timedelta
from xblock.core import ModelType from xblock.core import ModelType
import datetime import datetime
import dateutil.parser import dateutil.parser
from xblock.core import Integer, Float, Boolean from xblock.core import Integer, Float, Boolean
from django.utils.timezone import UTC
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class Date(ModelType): 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): def from_json(self, field):
""" """
...@@ -27,11 +27,15 @@ class Date(ModelType): ...@@ -27,11 +27,15 @@ class Date(ModelType):
elif field is "": elif field is "":
return None return None
elif isinstance(field, basestring): elif isinstance(field, basestring):
d = dateutil.parser.parse(field) result = dateutil.parser.parse(field)
return d.utctimetuple() if result.tzinfo is None:
result = result.replace(tzinfo=UTC())
return result
elif isinstance(field, (int, long, float)): elif isinstance(field, (int, long, float)):
return time.gmtime(field / 1000) return datetime.datetime.fromtimestamp(field / 1000, UTC())
elif isinstance(field, time.struct_time): elif isinstance(field, time.struct_time):
return datetime.datetime.fromtimestamp(time.mktime(field), UTC())
elif isinstance(field, datetime.datetime):
return field return field
else: else:
msg = "Field {0} has bad value '{1}'".format( msg = "Field {0} has bad value '{1}'".format(
...@@ -49,7 +53,11 @@ class Date(ModelType): ...@@ -49,7 +53,11 @@ class Date(ModelType):
# struct_times are always utc # struct_times are always utc
return time.strftime('%Y-%m-%dT%H:%M:%SZ', value) return time.strftime('%Y-%m-%dT%H:%M:%SZ', value)
elif isinstance(value, datetime.datetime): 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<days>\d+?) day(?:s?))?(\s)?((?P<hours>\d+?) hour(?:s?))?(\s)?((?P<minutes>\d+?) minute(?:s)?)?(\s)?((?P<seconds>\d+?) second(?:s)?)?$') TIMEDELTA_REGEX = re.compile(r'^((?P<days>\d+?) day(?:s?))?(\s)?((?P<hours>\d+?) hour(?:s?))?(\s)?((?P<minutes>\d+?) minute(?:s)?)?(\s)?((?P<seconds>\d+?) second(?:s)?)?$')
...@@ -74,7 +82,7 @@ class Timedelta(ModelType): ...@@ -74,7 +82,7 @@ class Timedelta(ModelType):
for (name, param) in parts.iteritems(): for (name, param) in parts.iteritems():
if param: if param:
time_params[name] = int(param) time_params[name] = int(param)
return timedelta(**time_params) return datetime.timedelta(**time_params)
def to_json(self, value): def to_json(self, value):
values = [] values = []
...@@ -93,7 +101,7 @@ class StringyInteger(Integer): ...@@ -93,7 +101,7 @@ class StringyInteger(Integer):
def from_json(self, value): def from_json(self, value):
try: try:
return int(value) return int(value)
except: except Exception:
return None return None
......
...@@ -8,7 +8,6 @@ from xmodule.x_module import XModule ...@@ -8,7 +8,6 @@ from xmodule.x_module import XModule
from xmodule.xml_module import XmlDescriptor from xmodule.xml_module import XmlDescriptor
from xblock.core import Scope, Integer, String from xblock.core import Scope, Integer, String
from .fields import Date from .fields import Date
from xmodule.util.date_utils import time_to_datetime
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -31,9 +30,7 @@ class FolditModule(FolditFields, XModule): ...@@ -31,9 +30,7 @@ class FolditModule(FolditFields, XModule):
css = {'scss': [resource_string(__name__, 'css/foldit/leaderboard.scss')]} css = {'scss': [resource_string(__name__, 'css/foldit/leaderboard.scss')]}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
XModule.__init__(self, *args, **kwargs)
""" """
Example: Example:
<foldit show_basic_score="true" <foldit show_basic_score="true"
required_level="4" required_level="4"
...@@ -42,8 +39,8 @@ class FolditModule(FolditFields, XModule): ...@@ -42,8 +39,8 @@ class FolditModule(FolditFields, XModule):
required_sublevel_half_credit="3" required_sublevel_half_credit="3"
show_leaderboard="false"/> show_leaderboard="false"/>
""" """
XModule.__init__(self, *args, **kwargs)
self.due_time = time_to_datetime(self.due) self.due_time = self.due
def is_complete(self): def is_complete(self):
""" """
...@@ -102,7 +99,7 @@ class FolditModule(FolditFields, XModule): ...@@ -102,7 +99,7 @@ class FolditModule(FolditFields, XModule):
from foldit.models import Score from foldit.models import Score
leaders = [(e['username'], e['score']) for e in Score.get_tops_n(10)] 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 return leaders
......
...@@ -4,6 +4,7 @@ from . import ModuleStoreBase, Location, namedtuple_to_son ...@@ -4,6 +4,7 @@ from . import ModuleStoreBase, Location, namedtuple_to_son
from .exceptions import ItemNotFoundError from .exceptions import ItemNotFoundError
from .inheritance import own_metadata from .inheritance import own_metadata
from xmodule.exceptions import InvalidVersionError from xmodule.exceptions import InvalidVersionError
from pytz import UTC
DRAFT = 'draft' DRAFT = 'draft'
# Things w/ these categories should never be marked as version='draft' # Things w/ these categories should never be marked as version='draft'
...@@ -197,7 +198,7 @@ class DraftModuleStore(ModuleStoreBase): ...@@ -197,7 +198,7 @@ class DraftModuleStore(ModuleStoreBase):
""" """
draft = self.get_item(location) 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 draft.cms.published_by = published_by_id
super(DraftModuleStore, self).update_item(location, draft._model_data._kvs._data) super(DraftModuleStore, self).update_item(location, draft._model_data._kvs._data)
super(DraftModuleStore, self).update_children(location, draft._model_data._kvs._children) super(DraftModuleStore, self).update_children(location, draft._model_data._kvs._children)
......
...@@ -52,7 +52,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem): ...@@ -52,7 +52,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
xmlstore: the XMLModuleStore to store the loaded modules in 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.used_names = defaultdict(set) # category -> set of used url_names
self.org, self.course, self.url_name = course_id.split('/') 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 # 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): ...@@ -124,7 +124,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
else: else:
# TODO (vshnayder): We may want to enable this once course repos are cleaned up. # 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...) # (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 pass
# Make sure everything is unique # Make sure everything is unique
...@@ -447,7 +447,7 @@ class XMLModuleStore(ModuleStoreBase): ...@@ -447,7 +447,7 @@ class XMLModuleStore(ModuleStoreBase):
def load_extra_content(self, system, course_descriptor, category, base_dir, course_dir, url_name): 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) 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): if os.path.isdir(base_dir / url_name):
self._load_extra_content(system, course_descriptor, category, base_dir / url_name, course_dir) self._load_extra_content(system, course_descriptor, category, base_dir / url_name, course_dir)
......
...@@ -16,6 +16,7 @@ from .peer_grading_service import PeerGradingService, MockPeerGradingService ...@@ -16,6 +16,7 @@ from .peer_grading_service import PeerGradingService, MockPeerGradingService
import controller_query_service import controller_query_service
from datetime import datetime from datetime import datetime
from django.utils.timezone import UTC
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
...@@ -56,7 +57,7 @@ class OpenEndedChild(object): ...@@ -56,7 +57,7 @@ class OpenEndedChild(object):
POST_ASSESSMENT = 'post_assessment' POST_ASSESSMENT = 'post_assessment'
DONE = 'done' 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 = { HUMAN_NAMES = {
'initial': 'Not started', 'initial': 'Not started',
'assessing': 'In progress', 'assessing': 'In progress',
...@@ -102,7 +103,7 @@ class OpenEndedChild(object): ...@@ -102,7 +103,7 @@ class OpenEndedChild(object):
if system.open_ended_grading_interface: if system.open_ended_grading_interface:
self.peer_gs = PeerGradingService(system.open_ended_grading_interface, system) self.peer_gs = PeerGradingService(system.open_ended_grading_interface, system)
self.controller_qs = controller_query_service.ControllerQueryService( self.controller_qs = controller_query_service.ControllerQueryService(
system.open_ended_grading_interface,system system.open_ended_grading_interface, system
) )
else: else:
self.peer_gs = MockPeerGradingService() self.peer_gs = MockPeerGradingService()
...@@ -130,7 +131,7 @@ class OpenEndedChild(object): ...@@ -130,7 +131,7 @@ class OpenEndedChild(object):
pass pass
def closed(self): 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 True
return False return False
...@@ -138,13 +139,13 @@ class OpenEndedChild(object): ...@@ -138,13 +139,13 @@ class OpenEndedChild(object):
if self.closed(): if self.closed():
return True, { return True, {
'success': False, '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.' 'error': 'The problem close date has passed, and this problem is now closed.'
} }
elif self.child_attempts > self.max_attempts: elif self.child_attempts > self.max_attempts:
return True, { return True, {
'success': False, '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( 'error': 'You have attempted this problem {0} times. You are allowed {1} attempts.'.format(
self.child_attempts, self.max_attempts self.child_attempts, self.max_attempts
) )
...@@ -272,7 +273,7 @@ class OpenEndedChild(object): ...@@ -272,7 +273,7 @@ class OpenEndedChild(object):
try: try:
return Progress(int(self.get_score()['score']), int(self._max_score)) return Progress(int(self.get_score()['score']), int(self._max_score))
except Exception as err: 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)) log.exception("Got bad progress from open ended child module. Max Score: {0}".format(self._max_score))
return None return None
return None return None
...@@ -281,10 +282,10 @@ class OpenEndedChild(object): ...@@ -281,10 +282,10 @@ class OpenEndedChild(object):
""" """
return dict out-of-sync error message, and also log. 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", log.warning("Open ended child state out sync. state: %r, get: %r. %s",
self.child_state, get, msg) self.child_state, get, msg)
#This is a student_facing_error # This is a student_facing_error
return {'success': False, return {'success': False,
'error': 'The problem state got out-of-sync. Please try reloading the page.'} 'error': 'The problem state got out-of-sync. Please try reloading the page.'}
...@@ -391,7 +392,7 @@ class OpenEndedChild(object): ...@@ -391,7 +392,7 @@ class OpenEndedChild(object):
""" """
overall_success = False overall_success = False
if not self.accept_file_upload: 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 return True, get_data
has_file_to_upload, uploaded_to_s3, image_ok, image_tag = self.check_for_image_and_upload(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): ...@@ -399,19 +400,19 @@ class OpenEndedChild(object):
get_data['student_answer'] += image_tag get_data['student_answer'] += image_tag
overall_success = True overall_success = True
elif has_file_to_upload and not uploaded_to_s3 and image_ok: 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 # 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" # 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, " 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" "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" "issue with this deployment, but it could also indicate a problem with S3 or with the"
"student image itself.") "student image itself.")
overall_success = True overall_success = True
elif not has_file_to_upload: 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']) success, get_data['student_answer'] = self.check_for_url_in_text(get_data['student_answer'])
overall_success = success 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 return overall_success, get_data
...@@ -441,7 +442,7 @@ class OpenEndedChild(object): ...@@ -441,7 +442,7 @@ class OpenEndedChild(object):
success = False success = False
allowed_to_submit = True allowed_to_submit = True
response = {} 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. " 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.") "You have graded {1}, and {2} are required. You have made {3} successful peer grading submissions.")
try: try:
...@@ -451,17 +452,17 @@ class OpenEndedChild(object): ...@@ -451,17 +452,17 @@ class OpenEndedChild(object):
student_sub_count = response['student_sub_count'] student_sub_count = response['student_sub_count']
success = True success = True
except: 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( log.error("Could not contact external open ended graders for location {0} and student {1}".format(
self.location_string, student_id)) 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." error_message = "Could not contact the graders. Please notify course staff."
return success, allowed_to_submit, error_message return success, allowed_to_submit, error_message
if count_graded >= count_required: if count_graded >= count_required:
return success, allowed_to_submit, "" return success, allowed_to_submit, ""
else: else:
allowed_to_submit = False 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, error_message = error_string.format(count_required - count_graded, count_graded, count_required,
student_sub_count) student_sub_count)
return success, allowed_to_submit, error_message return success, allowed_to_submit, error_message
......
...@@ -15,6 +15,7 @@ from xmodule.fields import Date, StringyFloat, StringyInteger, StringyBoolean ...@@ -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 xmodule.open_ended_grading_classes.peer_grading_service import PeerGradingService, GradingServiceError, MockPeerGradingService
from open_ended_grading_classes import combined_open_ended_rubric from open_ended_grading_classes import combined_open_ended_rubric
from django.utils.timezone import UTC
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -76,7 +77,7 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -76,7 +77,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
def __init__(self, system, location, descriptor, model_data): def __init__(self, system, location, descriptor, model_data):
XModule.__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) system.set('location', location)
self.system = system self.system = system
if (self.system.open_ended_grading_interface): if (self.system.open_ended_grading_interface):
...@@ -112,7 +113,7 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -112,7 +113,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
if not self.ajax_url.endswith("/"): if not self.ajax_url.endswith("/"):
self.ajax_url = self.ajax_url + "/" 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): if not isinstance(self.max_grade, int):
raise TypeError("max_grade needs to be an integer.") raise TypeError("max_grade needs to be an integer.")
...@@ -120,7 +121,7 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -120,7 +121,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
return self._closed(self.timeinfo) return self._closed(self.timeinfo)
def _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 True
return False return False
...@@ -166,9 +167,9 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -166,9 +167,9 @@ class PeerGradingModule(PeerGradingFields, XModule):
} }
if dispatch not in handlers: 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)) 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}) return json.dumps({'error': 'Error handling action. Please try again.', 'success': False})
d = handlers[dispatch](get) d = handlers[dispatch](get)
...@@ -187,7 +188,7 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -187,7 +188,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
count_required = response['count_required'] count_required = response['count_required']
success = True success = True
except GradingServiceError: 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}" log.exception("Error getting location data from controller for location {0}, student {1}"
.format(location, student_id)) .format(location, student_id))
...@@ -220,7 +221,7 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -220,7 +221,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
count_graded = response['count_graded'] count_graded = response['count_graded']
count_required = response['count_required'] count_required = response['count_required']
if count_required > 0 and count_graded >= 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 self.student_data_for_location = response
if self.weight is not None: if self.weight is not None:
...@@ -271,10 +272,10 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -271,10 +272,10 @@ class PeerGradingModule(PeerGradingFields, XModule):
response = self.peer_gs.get_next_submission(location, grader_id) response = self.peer_gs.get_next_submission(location, grader_id)
return response return response
except GradingServiceError: 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}" log.exception("Error getting next submission. server url: {0} location: {1}, grader_id: {2}"
.format(self.peer_gs.url, location, grader_id)) .format(self.peer_gs.url, location, grader_id))
#This is a student_facing_error # This is a student_facing_error
return {'success': False, return {'success': False,
'error': EXTERNAL_GRADER_NO_CONTACT_ERROR} 'error': EXTERNAL_GRADER_NO_CONTACT_ERROR}
...@@ -314,13 +315,13 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -314,13 +315,13 @@ class PeerGradingModule(PeerGradingFields, XModule):
score, feedback, submission_key, rubric_scores, submission_flagged) score, feedback, submission_key, rubric_scores, submission_flagged)
return response return response
except GradingServiceError: 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}, log.exception("""Error saving grade to open ended grading service. server url: {0}, location: {1}, submission_id:{2},
submission_key: {3}, score: {4}""" submission_key: {3}, score: {4}"""
.format(self.peer_gs.url, .format(self.peer_gs.url,
location, submission_id, submission_key, score) location, submission_id, submission_key, score)
) )
#This is a student_facing_error # This is a student_facing_error
return { return {
'success': False, 'success': False,
'error': EXTERNAL_GRADER_NO_CONTACT_ERROR 'error': EXTERNAL_GRADER_NO_CONTACT_ERROR
...@@ -356,10 +357,10 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -356,10 +357,10 @@ class PeerGradingModule(PeerGradingFields, XModule):
response = self.peer_gs.is_student_calibrated(location, grader_id) response = self.peer_gs.is_student_calibrated(location, grader_id)
return response return response
except GradingServiceError: 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}" log.exception("Error from open ended grading service. server url: {0}, grader_id: {0}, location: {1}"
.format(self.peer_gs.url, grader_id, location)) .format(self.peer_gs.url, grader_id, location))
#This is a student_facing_error # This is a student_facing_error
return { return {
'success': False, 'success': False,
'error': EXTERNAL_GRADER_NO_CONTACT_ERROR 'error': EXTERNAL_GRADER_NO_CONTACT_ERROR
...@@ -401,17 +402,17 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -401,17 +402,17 @@ class PeerGradingModule(PeerGradingFields, XModule):
response = self.peer_gs.show_calibration_essay(location, grader_id) response = self.peer_gs.show_calibration_essay(location, grader_id)
return response return response
except GradingServiceError: 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}" log.exception("Error from open ended grading service. server url: {0}, location: {0}"
.format(self.peer_gs.url, location)) .format(self.peer_gs.url, location))
#This is a student_facing_error # This is a student_facing_error
return {'success': False, return {'success': False,
'error': EXTERNAL_GRADER_NO_CONTACT_ERROR} 'error': EXTERNAL_GRADER_NO_CONTACT_ERROR}
# if we can't parse the rubric into HTML, # if we can't parse the rubric into HTML,
except etree.XMLSyntaxError: except etree.XMLSyntaxError:
#This is a dev_facing_error # This is a dev_facing_error
log.exception("Cannot parse rubric string.") log.exception("Cannot parse rubric string.")
#This is a student_facing_error # This is a student_facing_error
return {'success': False, return {'success': False,
'error': 'Error displaying submission. Please notify course staff.'} 'error': 'Error displaying submission. Please notify course staff.'}
...@@ -455,11 +456,11 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -455,11 +456,11 @@ class PeerGradingModule(PeerGradingFields, XModule):
response['actual_rubric'] = rubric_renderer.render_rubric(response['actual_rubric'])['html'] response['actual_rubric'] = rubric_renderer.render_rubric(response['actual_rubric'])['html']
return response return response
except GradingServiceError: except GradingServiceError:
#This is a dev_facing_error # This is a dev_facing_error
log.exception( log.exception(
"Error saving calibration grade, location: {0}, submission_key: {1}, grader_id: {2}".format( "Error saving calibration grade, location: {0}, submission_key: {1}, grader_id: {2}".format(
location, submission_key, grader_id)) 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.') return self._err_response('There was an error saving your score. Please notify course staff.')
def peer_grading_closed(self): def peer_grading_closed(self):
...@@ -491,13 +492,13 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -491,13 +492,13 @@ class PeerGradingModule(PeerGradingFields, XModule):
problem_list = problem_list_dict['problem_list'] problem_list = problem_list_dict['problem_list']
except GradingServiceError: except GradingServiceError:
#This is a student_facing_error # This is a student_facing_error
error_text = EXTERNAL_GRADER_NO_CONTACT_ERROR error_text = EXTERNAL_GRADER_NO_CONTACT_ERROR
log.error(error_text) log.error(error_text)
success = False success = False
# catch error if if the json loads fails # catch error if if the json loads fails
except ValueError: 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." error_text = "Could not get list of problems to peer grade. Please notify course staff."
log.error(error_text) log.error(error_text)
success = False success = False
...@@ -557,8 +558,8 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -557,8 +558,8 @@ class PeerGradingModule(PeerGradingFields, XModule):
''' '''
if get is None or get.get('location') is None: if get is None or get.get('location') is None:
if self.use_for_single_location not in TRUE_DICT: 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 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 a dev_facing_error
log.error( log.error(
"Peer grading problem in peer_grading_module called with no get parameters, but use_for_single_location is False.") "Peer grading problem in peer_grading_module called with no get parameters, but use_for_single_location is False.")
return {'html': "", 'success': False} return {'html': "", 'success': False}
......
import unittest import unittest
from time import strptime
import datetime import datetime
from fs.memoryfs import MemoryFS from fs.memoryfs import MemoryFS
...@@ -8,13 +7,13 @@ from mock import Mock, patch ...@@ -8,13 +7,13 @@ from mock import Mock, patch
from xmodule.modulestore.xml import ImportSystem, XMLModuleStore from xmodule.modulestore.xml import ImportSystem, XMLModuleStore
import xmodule.course_module import xmodule.course_module
from xmodule.util.date_utils import time_to_datetime from django.utils.timezone import UTC
ORG = 'test_org' ORG = 'test_org'
COURSE = 'test_course' 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): class DummySystem(ImportSystem):
...@@ -81,10 +80,10 @@ class IsNewCourseTestCase(unittest.TestCase): ...@@ -81,10 +80,10 @@ class IsNewCourseTestCase(unittest.TestCase):
Mock(wraps=datetime.datetime) Mock(wraps=datetime.datetime)
) )
mocked_datetime = datetime_patcher.start() 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) self.addCleanup(datetime_patcher.stop)
@patch('xmodule.course_module.time.gmtime') @patch('xmodule.course_module.datetime.now')
def test_sorting_score(self, gmtime_mock): def test_sorting_score(self, gmtime_mock):
gmtime_mock.return_value = NOW gmtime_mock.return_value = NOW
...@@ -125,7 +124,7 @@ class IsNewCourseTestCase(unittest.TestCase): ...@@ -125,7 +124,7 @@ class IsNewCourseTestCase(unittest.TestCase):
print "Comparing %s to %s" % (a, b) print "Comparing %s to %s" % (a, b)
assertion(a_score, b_score) 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): def test_start_date_text(self, gmtime_mock):
gmtime_mock.return_value = NOW gmtime_mock.return_value = NOW
......
# 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))
"""Tests for classes defined in fields.py.""" """Tests for classes defined in fields.py."""
import datetime import datetime
import unittest import unittest
from django.utils.timezone import UTC
from xmodule.fields import Date, StringyFloat, StringyInteger, StringyBoolean from xmodule.fields import Date, StringyFloat, StringyInteger, StringyBoolean
import time from django.utils.timezone import UTC
class DateTest(unittest.TestCase): class DateTest(unittest.TestCase):
date = Date() date = Date()
@staticmethod def compare_dates(self, dt1, dt2, expected_delta):
def struct_to_datetime(struct_time): self.assertEqual(dt1 - dt2, expected_delta, str(dt1) + "-"
return datetime.datetime(struct_time.tm_year, struct_time.tm_mon, + str(dt2) + "!=" + str(expected_delta))
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 test_from_json(self): def test_from_json(self):
'''Test conversion from iso compatible date strings to struct_time''' '''Test conversion from iso compatible date strings to struct_time'''
...@@ -55,10 +46,10 @@ class DateTest(unittest.TestCase): ...@@ -55,10 +46,10 @@ class DateTest(unittest.TestCase):
def test_old_due_date_format(self): def test_old_due_date_format(self):
current = datetime.datetime.today() current = datetime.datetime.today()
self.assertEqual( 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")) DateTest.date.from_json("March 12 12:00"))
self.assertEqual( 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")) DateTest.date.from_json("December 4 16:30"))
def test_to_json(self): def test_to_json(self):
...@@ -67,7 +58,7 @@ class DateTest(unittest.TestCase): ...@@ -67,7 +58,7 @@ class DateTest(unittest.TestCase):
''' '''
self.assertEqual( self.assertEqual(
DateTest.date.to_json( 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") "2012-12-31T23:59:59Z")
self.assertEqual( self.assertEqual(
DateTest.date.to_json( DateTest.date.to_json(
...@@ -76,7 +67,7 @@ class DateTest(unittest.TestCase): ...@@ -76,7 +67,7 @@ class DateTest(unittest.TestCase):
self.assertEqual( self.assertEqual(
DateTest.date.to_json( DateTest.date.to_json(
DateTest.date.from_json("2012-12-31T23:00:01-01:00")), 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): class StringyIntegerTest(unittest.TestCase):
......
...@@ -13,6 +13,8 @@ from xmodule.modulestore.inheritance import compute_inherited_metadata ...@@ -13,6 +13,8 @@ from xmodule.modulestore.inheritance import compute_inherited_metadata
from xmodule.fields import Date from xmodule.fields import Date
from .test_export import DATA_DIR from .test_export import DATA_DIR
import datetime
from django.utils.timezone import UTC
ORG = 'test_org' ORG = 'test_org'
COURSE = 'test_course' COURSE = 'test_course'
...@@ -40,7 +42,7 @@ class DummySystem(ImportSystem): ...@@ -40,7 +42,7 @@ class DummySystem(ImportSystem):
load_error_modules=load_error_modules, load_error_modules=load_error_modules,
) )
def render_template(self, template, context): def render_template(self, _template, _context):
raise Exception("Shouldn't be called") raise Exception("Shouldn't be called")
...@@ -62,6 +64,7 @@ class BaseCourseTestCase(unittest.TestCase): ...@@ -62,6 +64,7 @@ class BaseCourseTestCase(unittest.TestCase):
class ImportTestCase(BaseCourseTestCase): class ImportTestCase(BaseCourseTestCase):
date = Date()
def test_fallback(self): def test_fallback(self):
'''Check that malformed xml loads as an ErrorDescriptor.''' '''Check that malformed xml loads as an ErrorDescriptor.'''
...@@ -145,15 +148,18 @@ class ImportTestCase(BaseCourseTestCase): ...@@ -145,15 +148,18 @@ class ImportTestCase(BaseCourseTestCase):
descriptor = system.process_xml(start_xml) descriptor = system.process_xml(start_xml)
compute_inherited_metadata(descriptor) compute_inherited_metadata(descriptor)
# pylint: disable=W0212
print(descriptor, descriptor._model_data) 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 # Check that the child inherits due correctly
child = descriptor.get_children()[0] 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(child._inheritable_metadata, child._inherited_metadata)
self.assertEqual(2, len(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']) self.assertEqual(v, child._inherited_metadata['due'])
# Now export and check things # Now export and check things
...@@ -209,9 +215,13 @@ class ImportTestCase(BaseCourseTestCase): ...@@ -209,9 +215,13 @@ class ImportTestCase(BaseCourseTestCase):
# Check that the child does not inherit a value for due # Check that the child does not inherit a value for due
child = descriptor.get_children()[0] child = descriptor.get_children()[0]
self.assertEqual(child.lms.due, None) self.assertEqual(child.lms.due, None)
# pylint: disable=W0212
self.assertEqual(child._inheritable_metadata, child._inherited_metadata) self.assertEqual(child._inheritable_metadata, child._inherited_metadata)
self.assertEqual(1, len(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): def test_metadata_override_default(self):
""" """
...@@ -230,14 +240,17 @@ class ImportTestCase(BaseCourseTestCase): ...@@ -230,14 +240,17 @@ class ImportTestCase(BaseCourseTestCase):
</course>'''.format(due=course_due, org=ORG, course=COURSE, url_name=url_name) </course>'''.format(due=course_due, org=ORG, course=COURSE, url_name=url_name)
descriptor = system.process_xml(start_xml) descriptor = system.process_xml(start_xml)
child = descriptor.get_children()[0] child = descriptor.get_children()[0]
# pylint: disable=W0212
child._model_data['due'] = child_due child._model_data['due'] = child_due
compute_inherited_metadata(descriptor) compute_inherited_metadata(descriptor)
self.assertEqual(descriptor.lms.due, Date().from_json(course_due)) self.assertEqual(descriptor.lms.due, ImportTestCase.date.from_json(course_due))
self.assertEqual(child.lms.due, Date().from_json(child_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). # Test inherited metadata. Due does not appear here (because explicitly set on child).
self.assertEqual(1, len(child._inherited_metadata)) 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. # Test inheritable metadata. This has the course inheritable value for due.
self.assertEqual(2, len(child._inheritable_metadata)) self.assertEqual(2, len(child._inheritable_metadata))
self.assertEqual(course_due, child._inheritable_metadata['due']) self.assertEqual(course_due, child._inheritable_metadata['due'])
......
from .timeparse import parse_timedelta from .timeparse import parse_timedelta
from xmodule.util.date_utils import time_to_datetime
import logging import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -17,7 +16,7 @@ class TimeInfo(object): ...@@ -17,7 +16,7 @@ class TimeInfo(object):
""" """
def __init__(self, due_date, grace_period_string): def __init__(self, due_date, grace_period_string):
if due_date is not None: if due_date is not None:
self.display_due_date = time_to_datetime(due_date) self.display_due_date = due_date
else: else:
self.display_due_date = None self.display_due_date = None
......
""" """
Helper functions for handling time in the format we like. Helper functions for handling time in the format we like.
""" """
import time
import re import re
from datetime import timedelta from datetime import timedelta, datetime
TIME_FORMAT = "%Y-%m-%dT%H:%M" TIME_FORMAT = "%Y-%m-%dT%H:%M"
...@@ -17,14 +16,14 @@ def parse_time(time_str): ...@@ -17,14 +16,14 @@ def parse_time(time_str):
Raises ValueError if the string is not in the right format. 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): def parse_timedelta(time_str):
""" """
......
import time def get_default_time_display(dt, show_timezone=True):
import datetime
def get_default_time_display(time_struct, 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. 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", 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. 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. The default value of show_timezone is True.
""" """
timezone = "" if time_struct is None or not show_timezone else " UTC" timezone = ""
return get_time_struct_display(time_struct, "%b %d, %Y at %H:%M") + timezone if dt is not None and show_timezone:
if dt.tzinfo is not None:
try:
def get_time_struct_display(time_struct, format): timezone = dt.tzinfo.tzname(dt)
""" except NotImplementedError:
Converts a time struct to a string based on the given format. timezone = " UTC"
else:
If None is passed in, an empty string will be returned. timezone = " UTC"
""" return dt.strftime("%b %d, %Y at %H:%M") + timezone
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
<sequential> <sequential>
<vertical filename="vertical_58" slug="vertical_58" graceperiod="1 day 12 hours 59 minutes 59 seconds" showanswer="attempted" rerandomize="never"/> <vertical filename="vertical_58" slug="vertical_58"
<vertical slug="vertical_66" graceperiod="1 day 12 hours 59 minutes 59 seconds" showanswer="attempted" rerandomize="never"> graceperiod="1 day 12 hours 59 minutes 59 seconds" showanswer="attempted"
<problem filename="S1E3_AC_power" slug="S1E3_AC_power" name="S1E3: AC power"/> rerandomize="never" />
<customtag tag="S1E3" slug="discuss_67" impl="discuss"/> <vertical slug="vertical_66" graceperiod="1 day 12 hours 59 minutes 59 seconds"
<!-- utf-8 characters acceptable, but not HTML entities --> showanswer="attempted" rerandomize="never">
<html slug="html_68"> S1E4 has been removed…</html> <problem filename="S1E3_AC_power" slug="S1E3_AC_power"
</vertical> name="S1E3: AC power" />
<vertical filename="vertical_89" slug="vertical_89" graceperiod="1 day 12 hours 59 minutes 59 seconds" showanswer="attempted" rerandomize="never"/> <customtag tag="S1E3" slug="discuss_67" impl="discuss" />
<vertical slug="vertical_94" graceperiod="1 day 12 hours 59 minutes 59 seconds" showanswer="attempted" rerandomize="never"> <!-- utf-8 characters acceptable, but not HTML entities -->
<video youtube="0.75:XNh13VZhThQ,1.0:XbDRmF6J0K0,1.25:JDty12WEQWk,1.50:wELKGj-5iyM" slug="What_s_next" name="What's next"/> <html slug="html_68"> S1E4 has been removed…</html>
<html slug="html_95">Minor correction: Six elements (five resistors)…</html> </vertical>
<customtag tag="S1" slug="discuss_96" impl="discuss"/> <vertical filename="vertical_89" slug="vertical_89"
</vertical> graceperiod="1 day 12 hours 59 minutes 59 seconds" showanswer="attempted"
rerandomize="never" />
<vertical slug="vertical_94" graceperiod="1 day 12 hours 59 minutes 59 seconds"
showanswer="attempted" rerandomize="never">
<video
youtube="0.75:XNh13VZhThQ,1.0:XbDRmF6J0K0,1.25:JDty12WEQWk,1.50:wELKGj-5iyM"
slug="What_s_next" name="What's next" />
<html slug="html_95">Minor correction: Six elements (five resistors)…
</html>
<customtag tag="S1" slug="discuss_96" impl="discuss" />
</vertical>
<randomize url_name="PS1_Q4" display_name="Problem 4: Read a Molecule"> <randomize url_name="PS1_Q4" display_name="Problem 4: Read a Molecule">
<vertical> <vertical>
<html slug="html_900"> <html slug="html_900">
<!-- UTF-8 characters are acceptable… HTML entities are not --> <!-- UTF-8 characters are acceptable… HTML entities are not -->
<h1>Inline content…</h1> <h1>Inline content…</h1>
</html> </html>
</vertical> </vertical>
</randomize> </randomize>
</sequential> </sequential>
...@@ -16,6 +16,7 @@ from xmodule.x_module import XModule, XModuleDescriptor ...@@ -16,6 +16,7 @@ from xmodule.x_module import XModule, XModuleDescriptor
from student.models import CourseEnrollmentAllowed from student.models import CourseEnrollmentAllowed
from courseware.masquerade import is_masquerading_as_student from courseware.masquerade import is_masquerading_as_student
from django.utils.timezone import UTC
DEBUG_ACCESS = False DEBUG_ACCESS = False
...@@ -133,7 +134,7 @@ def _has_access_course_desc(user, course, action): ...@@ -133,7 +134,7 @@ def _has_access_course_desc(user, course, action):
(staff can always enroll) (staff can always enroll)
""" """
now = time.gmtime() now = datetime.now(UTC())
start = course.enrollment_start start = course.enrollment_start
end = course.enrollment_end end = course.enrollment_end
...@@ -242,7 +243,7 @@ def _has_access_descriptor(user, descriptor, action, course_context=None): ...@@ -242,7 +243,7 @@ def _has_access_descriptor(user, descriptor, action, course_context=None):
# Check start date # Check start date
if descriptor.lms.start is not None: if descriptor.lms.start is not None:
now = time.gmtime() now = datetime.now(UTC())
effective_start = _adjust_start_date_for_beta_testers(user, descriptor) effective_start = _adjust_start_date_for_beta_testers(user, descriptor)
if now > effective_start: if now > effective_start:
# after start date, everyone can see it # after start date, everyone can see it
...@@ -365,7 +366,7 @@ def _course_org_staff_group_name(location, course_context=None): ...@@ -365,7 +366,7 @@ def _course_org_staff_group_name(location, course_context=None):
def group_names_for(role, 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 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 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 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): ...@@ -483,8 +484,7 @@ def _adjust_start_date_for_beta_testers(user, descriptor):
non-None start date. non-None start date.
Returns: Returns:
A time, in the same format as returned by time.gmtime(). Either the same as A datetime. Either the same as start, or earlier for beta testers.
start, or earlier for beta testers.
NOTE: number of days to adjust should be cached to avoid looking it up thousands of NOTE: number of days to adjust should be cached to avoid looking it up thousands of
times per query. times per query.
...@@ -505,15 +505,11 @@ def _adjust_start_date_for_beta_testers(user, descriptor): ...@@ -505,15 +505,11 @@ def _adjust_start_date_for_beta_testers(user, descriptor):
beta_group = course_beta_test_group_name(descriptor.location) beta_group = course_beta_test_group_name(descriptor.location)
if beta_group in user_groups: if beta_group in user_groups:
debug("Adjust start time: user in group %s", beta_group) debug("Adjust start time: user in group %s", beta_group)
# time_structs don't support subtraction, so convert to datetimes, start_as_datetime = descriptor.lms.start
# 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])
delta = timedelta(descriptor.lms.days_early_for_beta) delta = timedelta(descriptor.lms.days_early_for_beta)
effective = start_as_datetime - delta effective = start_as_datetime - delta
# ...and back to time_struct # ...and back to time_struct
return effective.timetuple() return effective
return descriptor.lms.start return descriptor.lms.start
...@@ -564,7 +560,7 @@ def _has_access_to_location(user, location, access_level, course_context): ...@@ -564,7 +560,7 @@ def _has_access_to_location(user, location, access_level, course_context):
return True return True
debug("Deny: user not in groups %s", staff_groups) 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) + \ instructor_groups = group_names_for_instructor(location, course_context) + \
[_course_org_instructor_group_name(location, course_context)] [_course_org_instructor_group_name(location, course_context)]
for instructor_group in instructor_groups: for instructor_group in instructor_groups:
......
import unittest from mock import Mock, patch
import logging
import time
from mock import Mock, MagicMock, patch
from django.conf import settings
from django.test import TestCase from django.test import TestCase
from xmodule.course_module import CourseDescriptor
from xmodule.error_module import ErrorDescriptor
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.timeparse import parse_time
from xmodule.x_module import XModule, XModuleDescriptor
import courseware.access as access import courseware.access as access
from .factories import CourseEnrollmentAllowedFactory from .factories import CourseEnrollmentAllowedFactory
import datetime
from django.utils.timezone import UTC
class AccessTestCase(TestCase): class AccessTestCase(TestCase):
...@@ -77,7 +71,7 @@ class AccessTestCase(TestCase): ...@@ -77,7 +71,7 @@ class AccessTestCase(TestCase):
# TODO: override DISABLE_START_DATES and test the start date branch of the method # TODO: override DISABLE_START_DATES and test the start date branch of the method
u = Mock() u = Mock()
d = 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 # Always returns true because DISABLE_START_DATES is set in test.py
self.assertTrue(access._has_access_descriptor(u, d, 'load')) self.assertTrue(access._has_access_descriptor(u, d, 'load'))
...@@ -85,8 +79,8 @@ class AccessTestCase(TestCase): ...@@ -85,8 +79,8 @@ class AccessTestCase(TestCase):
def test__has_access_course_desc_can_enroll(self): def test__has_access_course_desc_can_enroll(self):
u = Mock() u = Mock()
yesterday = time.gmtime(time.time() - 86400) yesterday = datetime.datetime.now(UTC()) - datetime.timedelta(days=1)
tomorrow = time.gmtime(time.time() + 86400) tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1)
c = Mock(enrollment_start=yesterday, enrollment_end=tomorrow) c = Mock(enrollment_start=yesterday, enrollment_end=tomorrow)
# User can enroll if it is between the start and end dates # User can enroll if it is between the start and end dates
......
...@@ -3,7 +3,6 @@ Test for lms courseware app ...@@ -3,7 +3,6 @@ Test for lms courseware app
''' '''
import logging import logging
import json import json
import time
import random import random
from urlparse import urlsplit, urlunsplit from urlparse import urlsplit, urlunsplit
...@@ -30,6 +29,8 @@ from xmodule.modulestore.django import modulestore ...@@ -30,6 +29,8 @@ from xmodule.modulestore.django import modulestore
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.xml_importer import import_from_xml from xmodule.modulestore.xml_importer import import_from_xml
from xmodule.modulestore.xml import XMLModuleStore from xmodule.modulestore.xml import XMLModuleStore
import datetime
from django.utils.timezone import UTC
log = logging.getLogger("mitx." + __name__) log = logging.getLogger("mitx." + __name__)
...@@ -603,9 +604,9 @@ class TestViewAuth(LoginEnrollmentTestCase): ...@@ -603,9 +604,9 @@ class TestViewAuth(LoginEnrollmentTestCase):
"""Actually do the test, relying on settings to be right.""" """Actually do the test, relying on settings to be right."""
# Make courses start in the future # Make courses start in the future
tomorrow = time.time() + 24 * 3600 tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1)
self.toy.lms.start = time.gmtime(tomorrow) self.toy.lms.start = tomorrow
self.full.lms.start = time.gmtime(tomorrow) self.full.lms.start = tomorrow
self.assertFalse(self.toy.has_started()) self.assertFalse(self.toy.has_started())
self.assertFalse(self.full.has_started()) self.assertFalse(self.full.has_started())
...@@ -728,18 +729,18 @@ class TestViewAuth(LoginEnrollmentTestCase): ...@@ -728,18 +729,18 @@ class TestViewAuth(LoginEnrollmentTestCase):
"""Actually do the test, relying on settings to be right.""" """Actually do the test, relying on settings to be right."""
# Make courses start in the future # Make courses start in the future
tomorrow = time.time() + 24 * 3600 tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1)
nextday = tomorrow + 24 * 3600 nextday = tomorrow + datetime.timedelta(days=1)
yesterday = time.time() - 24 * 3600 yesterday = datetime.datetime.now(UTC()) - datetime.timedelta(days=1)
print "changing" print "changing"
# toy course's enrollment period hasn't started # toy course's enrollment period hasn't started
self.toy.enrollment_start = time.gmtime(tomorrow) self.toy.enrollment_start = tomorrow
self.toy.enrollment_end = time.gmtime(nextday) self.toy.enrollment_end = nextday
# full course's has # full course's has
self.full.enrollment_start = time.gmtime(yesterday) self.full.enrollment_start = yesterday
self.full.enrollment_end = time.gmtime(tomorrow) self.full.enrollment_end = tomorrow
print "login" print "login"
# First, try with an enrolled student # First, try with an enrolled student
...@@ -778,12 +779,10 @@ class TestViewAuth(LoginEnrollmentTestCase): ...@@ -778,12 +779,10 @@ class TestViewAuth(LoginEnrollmentTestCase):
self.assertFalse(settings.MITX_FEATURES['DISABLE_START_DATES']) self.assertFalse(settings.MITX_FEATURES['DISABLE_START_DATES'])
# Make courses start in the future # Make courses start in the future
tomorrow = time.time() + 24 * 3600 tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1)
# nextday = tomorrow + 24 * 3600
# yesterday = time.time() - 24 * 3600
# toy course's hasn't started # toy course's hasn't started
self.toy.lms.start = time.gmtime(tomorrow) self.toy.lms.start = tomorrow
self.assertFalse(self.toy.has_started()) self.assertFalse(self.toy.has_started())
# but should be accessible for beta testers # but should be accessible for beta testers
...@@ -854,7 +853,7 @@ class TestSubmittingProblems(LoginEnrollmentTestCase): ...@@ -854,7 +853,7 @@ class TestSubmittingProblems(LoginEnrollmentTestCase):
modx_url = self.modx_url(problem_location, 'problem_check') modx_url = self.modx_url(problem_location, 'problem_check')
answer_key_prefix = 'input_i4x-edX-{}-problem-{}_'.format(self.course_slug, problem_url_name) answer_key_prefix = 'input_i4x-edX-{}-problem-{}_'.format(self.course_slug, problem_url_name)
resp = self.client.post(modx_url, 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 return resp
...@@ -925,7 +924,7 @@ class TestCourseGrader(TestSubmittingProblems): ...@@ -925,7 +924,7 @@ class TestCourseGrader(TestSubmittingProblems):
# Only get half of the first problem correct # Only get half of the first problem correct
self.submit_question_answer('H1P1', {'2_1': 'Correct', '2_2': 'Incorrect'}) self.submit_question_answer('H1P1', {'2_1': 'Correct', '2_2': 'Incorrect'})
self.check_grade_percent(0.06) 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]) self.assertEqual(score_for_hw('Homework1'), [1.0, 0.0])
# Get both parts of the first problem correct # Get both parts of the first problem correct
...@@ -958,16 +957,16 @@ class TestCourseGrader(TestSubmittingProblems): ...@@ -958,16 +957,16 @@ class TestCourseGrader(TestSubmittingProblems):
# Third homework # Third homework
self.submit_question_answer('H3P1', {'2_1': 'Correct', '2_2': 'Correct'}) 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.assertEqual(earned_hw_scores(), [4.0, 4.0, 2.0])
self.submit_question_answer('H3P2', {'2_1': 'Correct', '2_2': 'Correct'}) 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]) self.assertEqual(earned_hw_scores(), [4.0, 4.0, 4.0])
# Now we answer the final question (worth half of the grade) # Now we answer the final question (worth half of the grade)
self.submit_question_answer('FinalQuestion', {'2_1': 'Correct', '2_2': 'Correct'}) 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) @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
...@@ -1000,7 +999,7 @@ class TestSchematicResponse(TestSubmittingProblems): ...@@ -1000,7 +999,7 @@ class TestSchematicResponse(TestSubmittingProblems):
{ '2_1': json.dumps( { '2_1': json.dumps(
[['transient', {'Z': [ [['transient', {'Z': [
[0.0000004, 2.8], [0.0000004, 2.8],
[0.0000009, 0.0], # wrong. [0.0000009, 0.0], # wrong.
[0.0000014, 2.8], [0.0000014, 2.8],
[0.0000019, 2.8], [0.0000019, 2.8],
[0.0000024, 2.8], [0.0000024, 2.8],
......
import time
from collections import defaultdict from collections import defaultdict
import logging import logging
import time
import urllib import urllib
from datetime import datetime from datetime import datetime
from courseware.module_render import get_module 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.contrib.auth.models import User
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import connection from django.db import connection
...@@ -16,13 +11,12 @@ from django.http import HttpResponse ...@@ -16,13 +11,12 @@ from django.http import HttpResponse
from django.utils import simplejson from django.utils import simplejson
from django_comment_common.models import Role from django_comment_common.models import Role
from django_comment_client.permissions import check_permissions_by_view from django_comment_client.permissions import check_permissions_by_view
from xmodule.modulestore.exceptions import NoPathToItem
from mitxmako import middleware from mitxmako import middleware
import pystache_custom as pystache import pystache_custom as pystache
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from django.utils.timezone import UTC
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -100,7 +94,7 @@ def get_discussion_category_map(course): ...@@ -100,7 +94,7 @@ def get_discussion_category_map(course):
def filter_unstarted_categories(category_map): def filter_unstarted_categories(category_map):
now = time.gmtime() now = datetime.now(UTC())
result_map = {} result_map = {}
...@@ -220,7 +214,7 @@ def initialize_discussion_info(course): ...@@ -220,7 +214,7 @@ def initialize_discussion_info(course):
for topic, entry in course.discussion_topics.items(): for topic, entry in course.discussion_topics.items():
category_map['entries'][topic] = {"id": entry["id"], category_map['entries'][topic] = {"id": entry["id"],
"sort_key": entry.get("sort_key", topic), "sort_key": entry.get("sort_key", topic),
"start_date": time.gmtime()} "start_date": datetime.now(UTC())}
sort_map_entries(category_map) sort_map_entries(category_map)
_DISCUSSIONINFO[course.id]['id_map'] = discussion_id_map _DISCUSSIONINFO[course.id]['id_map'] = discussion_id_map
...@@ -292,7 +286,7 @@ def get_ability(course_id, content, user): ...@@ -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"), '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): 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): ...@@ -310,7 +304,7 @@ def get_annotated_content_info(course_id, content, user, user_info):
'ability': get_ability(course_id, content, user), 'ability': get_ability(course_id, content, user),
} }
#TODO: RENAME # TODO: RENAME
def get_annotated_content_infos(course_id, thread, user, user_info): def get_annotated_content_infos(course_id, thread, user, user_info):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment