Commit 64b909c5 by Don Mitchell

Merge pull request #12 from edx/feature/dhm/timeconv

Feature/dhm/timeconv
parents 3d89b697 44061f9c
......@@ -9,34 +9,34 @@ from nose.tools import assert_equal
@step('I click the new section link$')
def i_click_new_section_link(step):
def i_click_new_section_link(_step):
link_css = 'a.new-courseware-section-button'
world.css_click(link_css)
@step('I enter the section name and click save$')
def i_save_section_name(step):
def i_save_section_name(_step):
save_section_name('My Section')
@step('I enter a section name with a quote and click save$')
def i_save_section_name_with_quote(step):
def i_save_section_name_with_quote(_step):
save_section_name('Section with "Quote"')
@step('I have added a new section$')
def i_have_added_new_section(step):
def i_have_added_new_section(_step):
add_section()
@step('I click the Edit link for the release date$')
def i_click_the_edit_link_for_the_release_date(step):
def i_click_the_edit_link_for_the_release_date(_step):
button_css = 'div.section-published-date a.edit-button'
world.css_click(button_css)
@step('I save a new section release date$')
def i_save_a_new_section_release_date(step):
def i_save_a_new_section_release_date(_step):
set_date_and_time('input.start-date.date.hasDatepicker', '12/25/2013',
'input.start-time.time.ui-timepicker-input', '00:00')
world.browser.click_link_by_text('Save')
......@@ -46,35 +46,35 @@ def i_save_a_new_section_release_date(step):
@step('I see my section on the Courseware page$')
def i_see_my_section_on_the_courseware_page(step):
def i_see_my_section_on_the_courseware_page(_step):
see_my_section_on_the_courseware_page('My Section')
@step('I see my section name with a quote on the Courseware page$')
def i_see_my_section_name_with_quote_on_the_courseware_page(step):
def i_see_my_section_name_with_quote_on_the_courseware_page(_step):
see_my_section_on_the_courseware_page('Section with "Quote"')
@step('I click to edit the section name$')
def i_click_to_edit_section_name(step):
def i_click_to_edit_section_name(_step):
world.css_click('span.section-name-span')
@step('I see the complete section name with a quote in the editor$')
def i_see_complete_section_name_with_quote_in_editor(step):
def i_see_complete_section_name_with_quote_in_editor(_step):
css = '.section-name-edit input[type=text]'
assert world.is_css_present(css)
assert_equal(world.browser.find_by_css(css).value, 'Section with "Quote"')
@step('the section does not exist$')
def section_does_not_exist(step):
def section_does_not_exist(_step):
css = 'h3[data-name="My Section"]'
assert world.is_css_not_present(css)
@step('I see a release date for my section$')
def i_see_a_release_date_for_my_section(step):
def i_see_a_release_date_for_my_section(_step):
import re
css = 'span.published-status'
......@@ -83,26 +83,32 @@ def i_see_a_release_date_for_my_section(step):
# e.g. 11/06/2012 at 16:25
msg = 'Will Release:'
date_regex = '[01][0-9]\/[0-3][0-9]\/[12][0-9][0-9][0-9]'
time_regex = '[0-2][0-9]:[0-5][0-9]'
match_string = '%s %s at %s' % (msg, date_regex, time_regex)
date_regex = r'(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d\d?, \d{4}'
if not re.search(date_regex, status_text):
print status_text, date_regex
time_regex = r'[0-2]\d:[0-5]\d( \w{3})?'
if not re.search(time_regex, status_text):
print status_text, time_regex
match_string = r'%s\s+%s at %s' % (msg, date_regex, time_regex)
if not re.match(match_string, status_text):
print status_text, match_string
assert re.match(match_string, status_text)
@step('I see a link to create a new subsection$')
def i_see_a_link_to_create_a_new_subsection(step):
def i_see_a_link_to_create_a_new_subsection(_step):
css = 'a.new-subsection-item'
assert world.is_css_present(css)
@step('the section release date picker is not visible$')
def the_section_release_date_picker_not_visible(step):
def the_section_release_date_picker_not_visible(_step):
css = 'div.edit-subsection-publish-settings'
assert not world.css_visible(css)
@step('the section release date is updated$')
def the_section_release_date_is_updated(step):
def the_section_release_date_is_updated(_step):
css = 'span.published-status'
status_text = world.css_text(css)
assert_equal(status_text, 'Will Release: 12/25/2013 at 00:00 UTC')
......
......@@ -271,7 +271,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
)
self.assertTrue(getattr(draft_problem, 'is_draft', False))
#now requery with depth
# now requery with depth
course = modulestore('draft').get_item(
Location(['i4x', 'edX', 'simple', 'course', '2012_Fall', None]),
depth=None
......@@ -539,7 +539,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
on_disk = loads(grading_policy.read())
self.assertEqual(on_disk, course.grading_policy)
#check for policy.json
# check for policy.json
self.assertTrue(filesystem.exists('policy.json'))
# compare what's on disk to what we have in the course module
......
......@@ -54,6 +54,7 @@ class CourseDetailsTestCase(CourseTestCase):
def test_virgin_fetch(self):
details = CourseDetails.fetch(self.course_location)
self.assertEqual(details.course_location, self.course_location, "Location not copied into")
self.assertIsNotNone(details.start_date.tzinfo)
self.assertIsNone(details.end_date, "end date somehow initialized " + str(details.end_date))
self.assertIsNone(details.enrollment_start, "enrollment_start date somehow initialized " + str(details.enrollment_start))
self.assertIsNone(details.enrollment_end, "enrollment_end date somehow initialized " + str(details.enrollment_end))
......@@ -67,7 +68,6 @@ class CourseDetailsTestCase(CourseTestCase):
jsondetails = json.dumps(details, cls=CourseSettingsEncoder)
jsondetails = json.loads(jsondetails)
self.assertTupleEqual(Location(jsondetails['course_location']), self.course_location, "Location !=")
# Note, start_date is being initialized someplace. I'm not sure why b/c the default will make no sense.
self.assertIsNone(jsondetails['end_date'], "end date somehow initialized ")
self.assertIsNone(jsondetails['enrollment_start'], "enrollment_start date somehow initialized ")
self.assertIsNone(jsondetails['enrollment_end'], "enrollment_end date somehow initialized ")
......@@ -76,6 +76,23 @@ class CourseDetailsTestCase(CourseTestCase):
self.assertIsNone(jsondetails['intro_video'], "intro_video somehow initialized")
self.assertIsNone(jsondetails['effort'], "effort somehow initialized")
def test_ooc_encoder(self):
"""
Test the encoder out of its original constrained purpose to see if it functions for general use
"""
details = {'location': Location(['tag', 'org', 'course', 'category', 'name']),
'number': 1,
'string': 'string',
'datetime': datetime.datetime.now(UTC())}
jsondetails = json.dumps(details, cls=CourseSettingsEncoder)
jsondetails = json.loads(jsondetails)
self.assertIn('location', jsondetails)
self.assertIn('org', jsondetails['location'])
self.assertEquals('org', jsondetails['location'][1])
self.assertEquals(1, jsondetails['number'])
self.assertEqual(jsondetails['string'], 'string')
def test_update_and_fetch(self):
# # NOTE: I couldn't figure out how to validly test time setting w/ all the conversions
jsondetails = CourseDetails.fetch(self.course_location)
......@@ -116,11 +133,8 @@ class CourseDetailsViewTest(CourseTestCase):
self.compare_details_with_encoding(json.loads(resp.content), details.__dict__, field + str(val))
@staticmethod
def convert_datetime_to_iso(datetime):
if datetime is not None:
return datetime.isoformat("T")
else:
return None
def convert_datetime_to_iso(dt):
return Date().to_json(dt)
def test_update_and_fetch(self):
details = CourseDetails.fetch(self.course_location)
......@@ -151,22 +165,12 @@ class CourseDetailsViewTest(CourseTestCase):
self.assertEqual(details['intro_video'], encoded.get('intro_video', None), context + " intro_video not ==")
self.assertEqual(details['effort'], encoded['effort'], context + " efforts not ==")
@staticmethod
def struct_to_datetime(struct_time):
return datetime.datetime(*struct_time[:6], tzinfo=UTC())
def compare_date_fields(self, details, encoded, context, field):
if details[field] is not None:
date = Date()
if field in encoded and encoded[field] is not None:
encoded_encoded = date.from_json(encoded[field])
dt1 = CourseDetailsViewTest.struct_to_datetime(encoded_encoded)
if isinstance(details[field], datetime.datetime):
dt1 = date.from_json(encoded[field])
dt2 = details[field]
else:
details_encoded = date.from_json(details[field])
dt2 = CourseDetailsViewTest.struct_to_datetime(details_encoded)
expected_delta = datetime.timedelta(0)
self.assertEqual(dt1 - dt2, expected_delta, str(dt1) + "!=" + str(dt2) + " at " + context)
......
......@@ -62,7 +62,7 @@ def asset_index(request, org, course, name):
asset_id = asset['_id']
display_info = {}
display_info['displayname'] = asset['displayname']
display_info['uploadDate'] = get_default_time_display(asset['uploadDate'].timetuple())
display_info['uploadDate'] = get_default_time_display(asset['uploadDate'])
asset_location = StaticContent.compute_location(asset_id['org'], asset_id['course'], asset_id['name'])
display_info['url'] = StaticContent.get_url_path_from_location(asset_location)
......@@ -131,7 +131,7 @@ def upload_asset(request, org, course, coursename):
readback = contentstore().find(content.location)
response_payload = {'displayname': content.name,
'uploadDate': get_default_time_display(readback.last_modified_at.timetuple()),
'uploadDate': get_default_time_display(readback.last_modified_at),
'url': StaticContent.get_url_path_from_location(content.location),
'thumb_url': StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_content is not None else None,
'msg': 'Upload completed'
......@@ -227,11 +227,9 @@ def generate_export_course(request, org, course, name):
root_dir = path(mkdtemp())
# export out to a tempdir
logging.debug('root = {0}'.format(root_dir))
export_to_xml(modulestore('direct'), contentstore(), loc, root_dir, name, modulestore())
#filename = root_dir / name + '.tar.gz'
logging.debug('tar file being generated at {0}'.format(export_file.name))
tar_file = tarfile.open(name=export_file.name, mode='w:gz')
......
......@@ -2,7 +2,6 @@
Views related to operations on course objects
"""
import json
import time
from django.contrib.auth.decorators import login_required
from django_future.csrf import ensure_csrf_cookie
......@@ -32,6 +31,8 @@ from .component import OPEN_ENDED_COMPONENT_TYPES, \
NOTE_COMPONENT_TYPES, ADVANCED_COMPONENT_POLICY_KEY
from django_comment_common.utils import seed_permissions_roles
import datetime
from django.utils.timezone import UTC
# TODO: should explicitly enumerate exports with __all__
......@@ -130,7 +131,7 @@ def create_new_course(request):
new_course.display_name = display_name
# set a default start date to now
new_course.start = time.gmtime()
new_course.start = datetime.datetime.now(UTC())
initialize_course_tabs(new_course)
......@@ -357,49 +358,49 @@ def course_advanced_updates(request, org, course, name):
# Whether or not to filter the tabs key out of the settings metadata
filter_tabs = True
#Check to see if the user instantiated any advanced components. This is a hack
#that does the following :
# Check to see if the user instantiated any advanced components. This is a hack
# that does the following :
# 1) adds/removes the open ended panel tab to a course automatically if the user
# has indicated that they want to edit the combinedopendended or peergrading module
# 2) adds/removes the notes panel tab to a course automatically if the user has
# indicated that they want the notes module enabled in their course
# TODO refactor the above into distinct advanced policy settings
if ADVANCED_COMPONENT_POLICY_KEY in request_body:
#Get the course so that we can scrape current tabs
# Get the course so that we can scrape current tabs
course_module = modulestore().get_item(location)
#Maps tab types to components
# Maps tab types to components
tab_component_map = {
'open_ended': OPEN_ENDED_COMPONENT_TYPES,
'notes': NOTE_COMPONENT_TYPES,
}
#Check to see if the user instantiated any notes or open ended components
# Check to see if the user instantiated any notes or open ended components
for tab_type in tab_component_map.keys():
component_types = tab_component_map.get(tab_type)
found_ac_type = False
for ac_type in component_types:
if ac_type in request_body[ADVANCED_COMPONENT_POLICY_KEY]:
#Add tab to the course if needed
# Add tab to the course if needed
changed, new_tabs = add_extra_panel_tab(tab_type, course_module)
#If a tab has been added to the course, then send the metadata along to CourseMetadata.update_from_json
# If a tab has been added to the course, then send the metadata along to CourseMetadata.update_from_json
if changed:
course_module.tabs = new_tabs
request_body.update({'tabs': new_tabs})
#Indicate that tabs should not be filtered out of the metadata
# Indicate that tabs should not be filtered out of the metadata
filter_tabs = False
#Set this flag to avoid the tab removal code below.
# Set this flag to avoid the tab removal code below.
found_ac_type = True
break
#If we did not find a module type in the advanced settings,
# If we did not find a module type in the advanced settings,
# we may need to remove the tab from the course.
if not found_ac_type:
#Remove tab from the course if needed
# Remove tab from the course if needed
changed, new_tabs = remove_extra_panel_tab(tab_type, course_module)
if changed:
course_module.tabs = new_tabs
request_body.update({'tabs': new_tabs})
#Indicate that tabs should *not* be filtered out of the metadata
# Indicate that tabs should *not* be filtered out of the metadata
filter_tabs = False
response_json = json.dumps(CourseMetadata.update_from_json(location,
......
......@@ -3,13 +3,13 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore.inheritance import own_metadata
import json
from json.encoder import JSONEncoder
import time
from contentstore.utils import get_modulestore
from models.settings import course_grading
from contentstore.utils import update_item
from xmodule.fields import Date
import re
import logging
import datetime
class CourseDetails(object):
......@@ -73,9 +73,9 @@ class CourseDetails(object):
"""
Decode the json into CourseDetails and save any changed attrs to the db
"""
## TODO make it an error for this to be undefined & for it to not be retrievable from modulestore
# TODO make it an error for this to be undefined & for it to not be retrievable from modulestore
course_location = jsondict['course_location']
## Will probably want to cache the inflight courses because every blur generates an update
# Will probably want to cache the inflight courses because every blur generates an update
descriptor = get_modulestore(course_location).get_item(course_location)
dirty = False
......@@ -181,7 +181,7 @@ class CourseSettingsEncoder(json.JSONEncoder):
return obj.__dict__
elif isinstance(obj, Location):
return obj.dict()
elif isinstance(obj, time.struct_time):
elif isinstance(obj, datetime.datetime):
return Date().to_json(obj)
else:
return JSONEncoder.default(self, obj)
......@@ -23,7 +23,7 @@ MODULESTORE_OPTIONS = {
'db': 'test_xmodule',
'collection': 'acceptance_modulestore',
'fs_root': TEST_ROOT / "data",
'render_template': 'mitxmako.shortcuts.render_to_string',
'render_template': 'mitxmako.shortcuts.render_to_string'
}
MODULESTORE = {
......
......@@ -25,6 +25,7 @@ Longer TODO:
import sys
import lms.envs.common
from lms.envs.common import USE_TZ
from path import path
############################ FEATURE CONFIGURATION #############################
......
......@@ -22,7 +22,7 @@ modulestore_options = {
'db': 'xmodule',
'collection': 'modulestore',
'fs_root': GITHUB_REPO_ROOT,
'render_template': 'mitxmako.shortcuts.render_to_string',
'render_template': 'mitxmako.shortcuts.render_to_string'
}
MODULESTORE = {
......@@ -64,7 +64,7 @@ REPOS = {
},
'content-mit-6002x': {
'branch': 'master',
#'origin': 'git@github.com:MITx/6002x-fall-2012.git',
# 'origin': 'git@github.com:MITx/6002x-fall-2012.git',
'origin': 'git@github.com:MITx/content-mit-6002x.git',
},
'6.00x': {
......
......@@ -48,7 +48,7 @@ MODULESTORE_OPTIONS = {
'db': 'test_xmodule',
'collection': 'test_modulestore',
'fs_root': TEST_ROOT / "data",
'render_template': 'mitxmako.shortcuts.render_to_string',
'render_template': 'mitxmako.shortcuts.render_to_string'
}
MODULESTORE = {
......@@ -121,7 +121,7 @@ CELERY_RESULT_BACKEND = 'cache'
BROKER_TRANSPORT = 'memory'
################### Make tests faster
#http://slacy.com/blog/2012/04/make-your-tests-faster-in-django-1-4/
# http://slacy.com/blog/2012/04/make-your-tests-faster-in-django-1-4/
PASSWORD_HASHERS = (
'django.contrib.auth.hashers.SHA1PasswordHasher',
'django.contrib.auth.hashers.MD5PasswordHasher',
......
'''
Used for pydev eclipse. Should be innocuous for everyone else.
Created on May 8, 2013
@author: dmitchell
'''
#!/home/<username>/mitx_all/python/bin/python
from django.core import management
if __name__ == '__main__':
management.execute_from_command_line()
<%inherit file="base.html" />
<%!
import logging
from xmodule.util.date_utils import get_time_struct_display
from xmodule.util.date_utils import get_default_time_display
%>
<%! from django.core.urlresolvers import reverse %>
......@@ -36,11 +36,15 @@
<div class="datepair" data-language="javascript">
<div class="field field-start-date">
<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') if subsection.lms.start else ''}"
placeholder="MM/DD/YYYY" class="date" size='15' autocomplete="off"/>
</div>
<div class="field field-start-time">
<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') if subsection.lms.start else ''}"
placeholder="HH:MM" class="time" size='10' autocomplete="off"/>
</div>
</div>
% if subsection.lms.start != parent_item.lms.start and subsection.lms.start:
......@@ -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.
% else:
<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
<a href="#" class="sync-date no-spinner">Sync to ${parent_item.display_name_with_default}.</a></p>
% endif
......@@ -65,11 +69,15 @@
<div class="datepair date-setter">
<div class="field field-start-date">
<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 class="field field-start-time">
<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>
<a href="#" class="remove-date">Remove due date</a>
</div>
......
<%inherit file="base.html" />
<%!
import logging
from xmodule.util.date_utils import get_time_struct_display
from xmodule.util import date_utils
%>
<%! from django.core.urlresolvers import reverse %>
<%block name="title">Course Outline</%block>
......@@ -154,14 +154,19 @@
<h3 class="section-name" data-name="${section.display_name_with_default | h}"></h3>
<div class="section-published-date">
<%
start_date_str = get_time_struct_display(section.lms.start, '%m/%d/%Y')
start_time_str = get_time_struct_display(section.lms.start, '%H:%M')
if section.lms.start is not None:
start_date_str = section.lms.start.strftime('%m/%d/%Y')
start_time_str = section.lms.start.strftime('%H:%M')
else:
start_date_str = ''
start_time_str = ''
%>
%if section.lms.start is None:
<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>
%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>
%endif
</div>
......
import logging
import time
from django.http import HttpResponse, Http404, HttpResponseNotModified
from django.http import HttpResponse, HttpResponseNotModified
from xmodule.contentstore.django import contentstore
from xmodule.contentstore.content import StaticContent, XASSET_LOCATION_TAG
......
......@@ -14,6 +14,7 @@ import sys
import datetime
import json
from pytz import UTC
middleware.MakoMiddleware()
......@@ -75,7 +76,7 @@ Will log what happened to file.txt.
utg = UserTestGroup()
utg.name = group
utg.description = json.dumps({"description": args[2]},
{"time": datetime.datetime.utcnow().isoformat()})
{"time": datetime.datetime.now(UTC).isoformat()})
group_objects[group] = utg
group_objects[group].save()
......
......@@ -8,6 +8,7 @@ from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from student.models import TestCenterUser
from pytz import UTC
class Command(BaseCommand):
......@@ -58,7 +59,7 @@ class Command(BaseCommand):
def handle(self, **options):
# update time should use UTC in order to be comparable to the user_updated_at
# field
uploaded_at = datetime.utcnow()
uploaded_at = datetime.now(UTC)
# if specified destination is an existing directory, then
# create a filename for it automatically. If it doesn't exist,
......
......@@ -8,6 +8,7 @@ from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from student.models import TestCenterRegistration, ACCOMMODATION_REJECTED_CODE
from pytz import UTC
class Command(BaseCommand):
......@@ -51,7 +52,7 @@ class Command(BaseCommand):
def handle(self, **options):
# update time should use UTC in order to be comparable to the user_updated_at
# field
uploaded_at = datetime.utcnow()
uploaded_at = datetime.now(UTC)
# if specified destination is an existing directory, then
# create a filename for it automatically. If it doesn't exist,
......
......@@ -13,6 +13,7 @@ from django.core.management.base import BaseCommand, CommandError
from django.conf import settings
from student.models import TestCenterUser, TestCenterRegistration
from pytz import UTC
class Command(BaseCommand):
......@@ -80,7 +81,7 @@ class Command(BaseCommand):
except ValueError as ve:
Command.datadog_error("Bad AuthorizationID value found for {}: message {}".format(client_authorization_id, ve), eacfile.name)
registration.confirmed_at = datetime.utcnow()
registration.confirmed_at = datetime.now(UTC)
registration.save()
except TestCenterRegistration.DoesNotExist:
Command.datadog_error("Failed to find record for client_auth_id {}".format(client_authorization_id), eacfile.name)
......
from optparse import make_option
from time import strftime
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand, CommandError
......@@ -128,8 +127,8 @@ class Command(BaseCommand):
exam = CourseDescriptor.TestCenterExam(course_id, exam_name, exam_info)
# update option values for date_first and date_last to use YYYY-MM-DD format
# instead of YYYY-MM-DDTHH:MM
our_options['eligibility_appointment_date_first'] = strftime("%Y-%m-%d", exam.first_eligible_appointment_date)
our_options['eligibility_appointment_date_last'] = strftime("%Y-%m-%d", exam.last_eligible_appointment_date)
our_options['eligibility_appointment_date_first'] = exam.first_eligible_appointment_date.strftime("%Y-%m-%d")
our_options['eligibility_appointment_date_last'] = exam.last_eligible_appointment_date.strftime("%Y-%m-%d")
if exam is None:
raise CommandError("Exam for course_id {} does not exist".format(course_id))
......
......@@ -16,7 +16,6 @@ import json
import logging
import uuid
from random import randint
from time import strftime
from django.conf import settings
......@@ -27,6 +26,7 @@ from django.dispatch import receiver
from django.forms import ModelForm, forms
import comment_client as cc
from pytz import UTC
log = logging.getLogger(__name__)
......@@ -54,7 +54,7 @@ class UserProfile(models.Model):
class Meta:
db_table = "auth_userprofile"
## CRITICAL TODO/SECURITY
# CRITICAL TODO/SECURITY
# Sanitize all fields.
# This is not visible to other users, but could introduce holes later
user = models.OneToOneField(User, unique=True, db_index=True, related_name='profile')
......@@ -254,7 +254,7 @@ class TestCenterUserForm(ModelForm):
def update_and_save(self):
new_user = self.save(commit=False)
# create additional values here:
new_user.user_updated_at = datetime.utcnow()
new_user.user_updated_at = datetime.now(UTC)
new_user.upload_status = ''
new_user.save()
log.info("Updated demographic information for user's test center exam registration: username \"{}\" ".format(new_user.user.username))
......@@ -429,8 +429,8 @@ class TestCenterRegistration(models.Model):
registration.course_id = exam.course_id
registration.accommodation_request = accommodation_request.strip()
registration.exam_series_code = exam.exam_series_code
registration.eligibility_appointment_date_first = strftime("%Y-%m-%d", exam.first_eligible_appointment_date)
registration.eligibility_appointment_date_last = strftime("%Y-%m-%d", exam.last_eligible_appointment_date)
registration.eligibility_appointment_date_first = exam.first_eligible_appointment_date.strftime("%Y-%m-%d")
registration.eligibility_appointment_date_last = exam.last_eligible_appointment_date.strftime("%Y-%m-%d")
registration.client_authorization_id = cls._create_client_authorization_id()
# accommodation_code remains blank for now, along with Pearson confirmation information
return registration
......@@ -556,7 +556,7 @@ class TestCenterRegistrationForm(ModelForm):
def update_and_save(self):
registration = self.save(commit=False)
# create additional values here:
registration.user_updated_at = datetime.utcnow()
registration.user_updated_at = datetime.now(UTC)
registration.upload_status = ''
registration.save()
log.info("Updated registration information for user's test center exam registration: username \"{}\" course \"{}\", examcode \"{}\"".format(registration.testcenter_user.user.username, registration.course_id, registration.exam_series_code))
......@@ -598,7 +598,7 @@ def unique_id_for_user(user):
return h.hexdigest()
## TODO: Should be renamed to generic UserGroup, and possibly
# TODO: Should be renamed to generic UserGroup, and possibly
# Given an optional field for type of group
class UserTestGroup(models.Model):
users = models.ManyToManyField(User, db_index=True)
......@@ -626,7 +626,6 @@ class Registration(models.Model):
def activate(self):
self.user.is_active = True
self.user.save()
#self.delete()
class PendingNameChange(models.Model):
......@@ -648,7 +647,7 @@ class CourseEnrollment(models.Model):
created = models.DateTimeField(auto_now_add=True, null=True, db_index=True)
class Meta:
unique_together = (('user', 'course_id'), )
unique_together = (('user', 'course_id'),)
def __unicode__(self):
return "[CourseEnrollment] %s: %s (%s)" % (self.user, self.course_id, self.created)
......@@ -667,12 +666,12 @@ class CourseEnrollmentAllowed(models.Model):
created = models.DateTimeField(auto_now_add=True, null=True, db_index=True)
class Meta:
unique_together = (('email', 'course_id'), )
unique_together = (('email', 'course_id'),)
def __unicode__(self):
return "[CourseEnrollmentAllowed] %s: %s (%s)" % (self.email, self.course_id, self.created)
#cache_relation(User.profile)
# cache_relation(User.profile)
#### Helper methods for use from python manage.py shell and other classes.
......
......@@ -49,6 +49,7 @@ from courseware.views import get_module_for_descriptor, jump_to
from courseware.model_data import ModelDataCache
from statsd import statsd
from pytz import UTC
log = logging.getLogger("mitx.student")
Article = namedtuple('Article', 'title url author image deck publication publish_date')
......@@ -668,7 +669,7 @@ def create_account(request, post_override=None):
if DoExternalAuth:
eamap.user = login_user
eamap.dtsignup = datetime.datetime.now()
eamap.dtsignup = datetime.datetime.now(UTC)
eamap.save()
log.debug('Updated ExternalAuthMap for %s to be %s' % (post_vars['username'], eamap))
......
'''
Created on Jun 6, 2013
@author: dmitchell
'''
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from student.tests.factories import AdminFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
import xmodule_modifiers
import datetime
from pytz import UTC
from xmodule.modulestore.tests import factories
class TestXmoduleModfiers(ModuleStoreTestCase):
# FIXME disabled b/c start date inheritance is not occuring and render_... in get_html is failing due
# to middleware.lookup['main'] not being defined
def _test_add_histogram(self):
instructor = AdminFactory.create()
self.client.login(username=instructor.username, password='test')
course = CourseFactory.create(org='test',
number='313', display_name='histogram test')
section = ItemFactory.create(
parent_location=course.location, display_name='chapter hist',
template='i4x://edx/templates/chapter/Empty')
problem = ItemFactory.create(
parent_location=section.location, display_name='problem hist 1',
template='i4x://edx/templates/problem/Blank_Common_Problem')
problem.has_score = False # don't trip trying to retrieve db data
late_problem = ItemFactory.create(
parent_location=section.location, display_name='problem hist 2',
template='i4x://edx/templates/problem/Blank_Common_Problem')
late_problem.lms.start = datetime.datetime.now(UTC) + datetime.timedelta(days=32)
late_problem.has_score = False
problem_module = factories.get_test_xmodule_for_descriptor(problem)
problem_module.get_html = xmodule_modifiers.add_histogram(lambda:'', problem_module, instructor)
self.assertRegexpMatches(
problem_module.get_html(), r'.*<font color=\'green\'>Not yet</font>.*')
problem_module = factories.get_test_xmodule_for_descriptor(late_problem)
problem_module.get_html = xmodule_modifiers.add_histogram(lambda: '', problem_module, instructor)
self.assertRegexpMatches(
problem_module.get_html(), r'.*<font color=\'red\'>Yes!</font>.*')
......@@ -14,6 +14,7 @@ from mitxmako.shortcuts import render_to_response
from django_future.csrf import ensure_csrf_cookie
from track.models import TrackingLog
from pytz import UTC
log = logging.getLogger("tracking")
......@@ -59,7 +60,7 @@ def user_track(request):
"event": request.GET['event'],
"agent": agent,
"page": request.GET['page'],
"time": datetime.datetime.utcnow().isoformat(),
"time": datetime.datetime.now(UTC).isoformat(),
"host": request.META['SERVER_NAME'],
}
log_event(event)
......@@ -85,7 +86,7 @@ def server_track(request, event_type, event, page=None):
"event": event,
"agent": agent,
"page": page,
"time": datetime.datetime.utcnow().isoformat(),
"time": datetime.datetime.now(UTC).isoformat(),
"host": request.META['SERVER_NAME'],
}
......
import re
import json
import logging
import time
import static_replace
from django.conf import settings
......@@ -9,6 +8,8 @@ from functools import wraps
from mitxmako.shortcuts import render_to_string
from xmodule.seq_module import SequenceModule
from xmodule.vertical_module import VerticalModule
import datetime
from django.utils.timezone import UTC
log = logging.getLogger("mitx.xmodule_modifiers")
......@@ -132,7 +133,7 @@ def add_histogram(get_html, module, user):
# useful to indicate to staff if problem has been released or not
# TODO (ichuang): use _has_access_descriptor.can_load in lms.courseware.access, instead of now>mstart comparison here
now = time.gmtime()
now = datetime.datetime.now(UTC())
is_released = "unknown"
mstart = module.descriptor.lms.start
......
......@@ -144,11 +144,11 @@ class InputTypeBase(object):
self.tag = xml.tag
self.system = system
## NOTE: ID should only come from one place. If it comes from multiple,
## we use state first, XML second (in case the xml changed, but we have
## existing state with an old id). Since we don't make this guarantee,
## we can swap this around in the future if there's a more logical
## order.
# NOTE: ID should only come from one place. If it comes from multiple,
# we use state first, XML second (in case the xml changed, but we have
# existing state with an old id). Since we don't make this guarantee,
# we can swap this around in the future if there's a more logical
# order.
self.input_id = state.get('id', xml.get('id'))
if self.input_id is None:
......@@ -769,7 +769,7 @@ class MatlabInput(CodeInput):
# construct xqueue headers
qinterface = self.system.xqueue['interface']
qtime = datetime.strftime(datetime.utcnow(), xqueue_interface.dateformat)
qtime = datetime.utcnow().strftime(xqueue_interface.dateformat)
callback_url = self.system.xqueue['construct_callback']('ungraded_response')
anonymous_student_id = self.system.anonymous_student_id
queuekey = xqueue_interface.make_hashkey(str(self.system.seed) + qtime +
......
......@@ -11,7 +11,7 @@ import sys
from pkg_resources import resource_string
from capa.capa_problem import LoncapaProblem
from capa.responsetypes import StudentInputError,\
from capa.responsetypes import StudentInputError, \
ResponseError, LoncapaProblemError
from capa.util import convert_files_to_filenames
from .progress import Progress
......@@ -20,7 +20,7 @@ from xmodule.raw_module import RawDescriptor
from xmodule.exceptions import NotFoundError, ProcessingError
from xblock.core import Scope, String, Boolean, Object
from .fields import Timedelta, Date, StringyInteger, StringyFloat
from xmodule.util.date_utils import time_to_datetime
from django.utils.timezone import UTC
log = logging.getLogger("mitx.courseware")
......@@ -134,7 +134,7 @@ class CapaModule(CapaFields, XModule):
def __init__(self, system, location, descriptor, model_data):
XModule.__init__(self, system, location, descriptor, model_data)
due_date = time_to_datetime(self.due)
due_date = self.due
if self.graceperiod is not None and due_date:
self.close_date = due_date + self.graceperiod
......@@ -502,7 +502,7 @@ class CapaModule(CapaFields, XModule):
Is it now past this problem's due date, including grace period?
"""
return (self.close_date is not None and
datetime.datetime.utcnow() > self.close_date)
datetime.datetime.now(UTC()) > self.close_date)
def closed(self):
''' Is the student still allowed to submit answers? '''
......@@ -747,7 +747,7 @@ class CapaModule(CapaFields, XModule):
# Problem queued. Students must wait a specified waittime before they are allowed to submit
if self.lcp.is_queued():
current_time = datetime.datetime.now()
current_time = datetime.datetime.now(UTC())
prev_submit_time = self.lcp.get_recentmost_queuetime()
waittime_between_requests = self.system.xqueue['waittime']
if (current_time - prev_submit_time).total_seconds() < waittime_between_requests:
......
......@@ -4,7 +4,6 @@ from math import exp
from lxml import etree
from path import path # NOTE (THK): Only used for detecting presence of syllabus
import requests
import time
from datetime import datetime
import dateutil.parser
......@@ -14,11 +13,12 @@ from xmodule.seq_module import SequenceDescriptor, SequenceModule
from xmodule.timeparse import parse_time
from xmodule.util.decorators import lazyproperty
from xmodule.graders import grader_from_conf
from xmodule.util.date_utils import time_to_datetime
import json
from xblock.core import Scope, List, String, Object, Boolean
from .fields import Date
from django.utils.timezone import UTC
from xmodule.util import date_utils
log = logging.getLogger(__name__)
......@@ -93,7 +93,7 @@ class Textbook(object):
# see if we already fetched this
if toc_url in _cached_toc:
(table_of_contents, timestamp) = _cached_toc[toc_url]
age = datetime.now() - timestamp
age = datetime.now(UTC) - timestamp
# expire every 10 minutes
if age.seconds < 600:
return table_of_contents
......@@ -219,8 +219,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
msg = None
if self.start is None:
msg = "Course loaded without a valid start date. id = %s" % self.id
# hack it -- start in 1970
self.start = time.gmtime(0)
self.start = datetime.now(UTC())
log.critical(msg)
self.system.error_tracker(msg)
......@@ -403,10 +402,10 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
if self.end is None:
return False
return time.gmtime() > self.end
return datetime.now(UTC()) > self.end
def has_started(self):
return time.gmtime() > self.start
return datetime.now(UTC()) > self.start
@property
def grader(self):
......@@ -547,14 +546,16 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
announcement = self.announcement
if announcement is not None:
announcement = time_to_datetime(announcement)
announcement = announcement
try:
start = dateutil.parser.parse(self.advertised_start)
if start.tzinfo is None:
start = start.replace(tzinfo=UTC())
except (ValueError, AttributeError):
start = time_to_datetime(self.start)
start = self.start
now = datetime.utcnow()
now = datetime.now(UTC())
return announcement, start, now
......@@ -656,7 +657,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
elif self.advertised_start is None and self.start is None:
return 'TBD'
else:
return time.strftime("%b %d, %Y", self.advertised_start or self.start)
return (self.advertised_start or self.start).strftime("%b %d, %Y")
@property
def end_date_text(self):
......@@ -665,7 +666,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
If the course does not have an end date set (course.end is None), an empty string will be returned.
"""
return '' if self.end is None else time.strftime("%b %d, %Y", self.end)
return '' if self.end is None else self.end.strftime("%b %d, %Y")
@property
def forum_posts_allowed(self):
......@@ -673,7 +674,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
blackout_periods = [(parse_time(start), parse_time(end))
for start, end
in self.discussion_blackouts]
now = time.gmtime()
now = datetime.now(UTC())
for start, end in blackout_periods:
if start <= now <= end:
return False
......@@ -699,7 +700,8 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
self.last_eligible_appointment_date = self._try_parse_time('Last_Eligible_Appointment_Date') # or self.first_eligible_appointment_date
if self.last_eligible_appointment_date is None:
raise ValueError("Last appointment date must be specified")
self.registration_start_date = self._try_parse_time('Registration_Start_Date') or time.gmtime(0)
self.registration_start_date = (self._try_parse_time('Registration_Start_Date') or
datetime.utcfromtimestamp(0))
self.registration_end_date = self._try_parse_time('Registration_End_Date') or self.last_eligible_appointment_date
# do validation within the exam info:
if self.registration_start_date > self.registration_end_date:
......@@ -725,32 +727,32 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
return None
def has_started(self):
return time.gmtime() > self.first_eligible_appointment_date
return datetime.now(UTC()) > self.first_eligible_appointment_date
def has_ended(self):
return time.gmtime() > self.last_eligible_appointment_date
return datetime.now(UTC()) > self.last_eligible_appointment_date
def has_started_registration(self):
return time.gmtime() > self.registration_start_date
return datetime.now(UTC()) > self.registration_start_date
def has_ended_registration(self):
return time.gmtime() > self.registration_end_date
return datetime.now(UTC()) > self.registration_end_date
def is_registering(self):
now = time.gmtime()
now = datetime.now(UTC())
return now >= self.registration_start_date and now <= self.registration_end_date
@property
def first_eligible_appointment_date_text(self):
return time.strftime("%b %d, %Y", self.first_eligible_appointment_date)
return self.first_eligible_appointment_date.strftime("%b %d, %Y")
@property
def last_eligible_appointment_date_text(self):
return time.strftime("%b %d, %Y", self.last_eligible_appointment_date)
return self.last_eligible_appointment_date.strftime("%b %d, %Y")
@property
def registration_end_date_text(self):
return time.strftime("%b %d, %Y at %H:%M UTC", self.registration_end_date)
return date_utils.get_default_time_display(self.registration_end_date)
@property
def current_test_center_exam(self):
......
......@@ -2,19 +2,19 @@ import time
import logging
import re
from datetime import timedelta
from xblock.core import ModelType
import datetime
import dateutil.parser
from xblock.core import Integer, Float, Boolean
from django.utils.timezone import UTC
log = logging.getLogger(__name__)
class Date(ModelType):
'''
Date fields know how to parse and produce json (iso) compatible formats.
Date fields know how to parse and produce json (iso) compatible formats. Converts to tz aware datetimes.
'''
def from_json(self, field):
"""
......@@ -27,11 +27,15 @@ class Date(ModelType):
elif field is "":
return None
elif isinstance(field, basestring):
d = dateutil.parser.parse(field)
return d.utctimetuple()
result = dateutil.parser.parse(field)
if result.tzinfo is None:
result = result.replace(tzinfo=UTC())
return result
elif isinstance(field, (int, long, float)):
return time.gmtime(field / 1000)
return datetime.datetime.fromtimestamp(field / 1000, UTC())
elif isinstance(field, time.struct_time):
return datetime.datetime.fromtimestamp(time.mktime(field), UTC())
elif isinstance(field, datetime.datetime):
return field
else:
msg = "Field {0} has bad value '{1}'".format(
......@@ -49,7 +53,11 @@ class Date(ModelType):
# struct_times are always utc
return time.strftime('%Y-%m-%dT%H:%M:%SZ', value)
elif isinstance(value, datetime.datetime):
return value.isoformat() + 'Z'
if value.tzinfo is None or value.utcoffset().total_seconds() == 0:
# isoformat adds +00:00 rather than Z
return value.strftime('%Y-%m-%dT%H:%M:%SZ')
else:
return value.isoformat()
TIMEDELTA_REGEX = re.compile(r'^((?P<days>\d+?) day(?:s?))?(\s)?((?P<hours>\d+?) hour(?:s?))?(\s)?((?P<minutes>\d+?) minute(?:s)?)?(\s)?((?P<seconds>\d+?) second(?:s)?)?$')
......@@ -66,6 +74,8 @@ class Timedelta(ModelType):
Returns a datetime.timedelta parsed from the string
"""
if time_str is None:
return None
parts = TIMEDELTA_REGEX.match(time_str)
if not parts:
return
......@@ -74,7 +84,7 @@ class Timedelta(ModelType):
for (name, param) in parts.iteritems():
if param:
time_params[name] = int(param)
return timedelta(**time_params)
return datetime.timedelta(**time_params)
def to_json(self, value):
values = []
......@@ -93,7 +103,7 @@ class StringyInteger(Integer):
def from_json(self, value):
try:
return int(value)
except:
except Exception:
return None
......
......@@ -8,7 +8,6 @@ from xmodule.x_module import XModule
from xmodule.xml_module import XmlDescriptor
from xblock.core import Scope, Integer, String
from .fields import Date
from xmodule.util.date_utils import time_to_datetime
log = logging.getLogger(__name__)
......@@ -31,9 +30,7 @@ class FolditModule(FolditFields, XModule):
css = {'scss': [resource_string(__name__, 'css/foldit/leaderboard.scss')]}
def __init__(self, *args, **kwargs):
XModule.__init__(self, *args, **kwargs)
"""
Example:
<foldit show_basic_score="true"
required_level="4"
......@@ -42,8 +39,8 @@ class FolditModule(FolditFields, XModule):
required_sublevel_half_credit="3"
show_leaderboard="false"/>
"""
self.due_time = time_to_datetime(self.due)
XModule.__init__(self, *args, **kwargs)
self.due_time = self.due
def is_complete(self):
"""
......@@ -102,7 +99,7 @@ class FolditModule(FolditFields, XModule):
from foldit.models import Score
leaders = [(e['username'], e['score']) for e in Score.get_tops_n(10)]
leaders.sort(key=lambda x: -x[1])
leaders.sort(key=lambda x:-x[1])
return leaders
......
......@@ -4,6 +4,7 @@ from . import ModuleStoreBase, Location, namedtuple_to_son
from .exceptions import ItemNotFoundError
from .inheritance import own_metadata
from xmodule.exceptions import InvalidVersionError
from pytz import UTC
DRAFT = 'draft'
# Things w/ these categories should never be marked as version='draft'
......@@ -197,7 +198,7 @@ class DraftModuleStore(ModuleStoreBase):
"""
draft = self.get_item(location)
draft.cms.published_date = datetime.utcnow()
draft.cms.published_date = datetime.now(UTC)
draft.cms.published_by = published_by_id
super(DraftModuleStore, self).update_item(location, draft._model_data._kvs._data)
super(DraftModuleStore, self).update_children(location, draft._model_data._kvs._children)
......
......@@ -231,6 +231,7 @@ class MongoModuleStore(ModuleStoreBase):
self.collection = pymongo.connection.Connection(
host=host,
port=port,
tz_aware=True,
**kwargs
)[db][collection]
......
......@@ -4,6 +4,11 @@ from uuid import uuid4
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.inheritance import own_metadata
from xmodule.x_module import ModuleSystem
from mitxmako.shortcuts import render_to_string
from xblock.runtime import InvalidScopeError
import datetime
from pytz import UTC
class XModuleCourseFactory(Factory):
......@@ -35,7 +40,7 @@ class XModuleCourseFactory(Factory):
if display_name is not None:
new_course.display_name = display_name
new_course.lms.start = gmtime()
new_course.lms.start = datetime.datetime.now(UTC)
new_course.tabs = kwargs.get(
'tabs',
[
......@@ -159,3 +164,32 @@ class ItemFactory(XModuleItemFactory):
@lazy_attribute_sequence
def display_name(attr, n):
return "{} {}".format(attr.category.title(), n)
def get_test_xmodule_for_descriptor(descriptor):
"""
Attempts to create an xmodule which responds usually correctly from the descriptor. Not guaranteed.
:param descriptor:
"""
module_sys = ModuleSystem(
ajax_url='',
track_function=None,
get_module=None,
render_template=render_to_string,
replace_urls=None,
xblock_model_data=_test_xblock_model_data_accessor(descriptor)
)
return descriptor.xmodule(module_sys)
def _test_xblock_model_data_accessor(descriptor):
simple_map = {}
for field in descriptor.fields:
try:
simple_map[field.name] = getattr(descriptor, field.name)
except InvalidScopeError:
simple_map[field.name] = field.default
for field in descriptor.module_class.fields:
if field.name not in simple_map:
simple_map[field.name] = field.default
return lambda o: simple_map
......@@ -19,7 +19,7 @@ DB = 'test'
COLLECTION = 'modulestore'
FS_ROOT = DATA_DIR # TODO (vshnayder): will need a real fs_root for testing load_item
DEFAULT_CLASS = 'xmodule.raw_module.RawDescriptor'
RENDER_TEMPLATE = lambda t_n, d, ctx=None, nsp='main': ''
RENDER_TEMPLATE = lambda t_n, d, ctx = None, nsp = 'main': ''
class TestMongoModuleStore(object):
......@@ -42,7 +42,8 @@ class TestMongoModuleStore(object):
@staticmethod
def initdb():
# connect to the db
store = MongoModuleStore(HOST, DB, COLLECTION, FS_ROOT, RENDER_TEMPLATE, default_class=DEFAULT_CLASS)
store = MongoModuleStore(HOST, DB, COLLECTION, FS_ROOT, RENDER_TEMPLATE,
default_class=DEFAULT_CLASS)
# Explicitly list the courses to load (don't want the big one)
courses = ['toy', 'simple']
import_from_xml(store, DATA_DIR, courses)
......
......@@ -124,7 +124,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
else:
# TODO (vshnayder): We may want to enable this once course repos are cleaned up.
# (or we may want to give up on the requirement for non-state-relevant issues...)
#error_tracker("WARNING: no name specified for module. xml='{0}...'".format(xml[:100]))
# error_tracker("WARNING: no name specified for module. xml='{0}...'".format(xml[:100]))
pass
# Make sure everything is unique
......
......@@ -16,6 +16,7 @@ from .peer_grading_service import PeerGradingService, MockPeerGradingService
import controller_query_service
from datetime import datetime
from django.utils.timezone import UTC
log = logging.getLogger("mitx.courseware")
......@@ -56,7 +57,7 @@ class OpenEndedChild(object):
POST_ASSESSMENT = 'post_assessment'
DONE = 'done'
#This is used to tell students where they are at in the module
# This is used to tell students where they are at in the module
HUMAN_NAMES = {
'initial': 'Not started',
'assessing': 'In progress',
......@@ -102,7 +103,7 @@ class OpenEndedChild(object):
if system.open_ended_grading_interface:
self.peer_gs = PeerGradingService(system.open_ended_grading_interface, system)
self.controller_qs = controller_query_service.ControllerQueryService(
system.open_ended_grading_interface,system
system.open_ended_grading_interface, system
)
else:
self.peer_gs = MockPeerGradingService()
......@@ -130,7 +131,7 @@ class OpenEndedChild(object):
pass
def closed(self):
if self.close_date is not None and datetime.utcnow() > self.close_date:
if self.close_date is not None and datetime.now(UTC()) > self.close_date:
return True
return False
......@@ -138,13 +139,13 @@ class OpenEndedChild(object):
if self.closed():
return True, {
'success': False,
#This is a student_facing_error
# This is a student_facing_error
'error': 'The problem close date has passed, and this problem is now closed.'
}
elif self.child_attempts > self.max_attempts:
return True, {
'success': False,
#This is a student_facing_error
# This is a student_facing_error
'error': 'You have attempted this problem {0} times. You are allowed {1} attempts.'.format(
self.child_attempts, self.max_attempts
)
......@@ -272,7 +273,7 @@ class OpenEndedChild(object):
try:
return Progress(int(self.get_score()['score']), int(self._max_score))
except Exception as err:
#This is a dev_facing_error
# This is a dev_facing_error
log.exception("Got bad progress from open ended child module. Max Score: {0}".format(self._max_score))
return None
return None
......@@ -281,10 +282,10 @@ class OpenEndedChild(object):
"""
return dict out-of-sync error message, and also log.
"""
#This is a dev_facing_error
# This is a dev_facing_error
log.warning("Open ended child state out sync. state: %r, get: %r. %s",
self.child_state, get, msg)
#This is a student_facing_error
# This is a student_facing_error
return {'success': False,
'error': 'The problem state got out-of-sync. Please try reloading the page.'}
......@@ -391,7 +392,7 @@ class OpenEndedChild(object):
"""
overall_success = False
if not self.accept_file_upload:
#If the question does not accept file uploads, do not do anything
# If the question does not accept file uploads, do not do anything
return True, get_data
has_file_to_upload, uploaded_to_s3, image_ok, image_tag = self.check_for_image_and_upload(get_data)
......@@ -399,19 +400,19 @@ class OpenEndedChild(object):
get_data['student_answer'] += image_tag
overall_success = True
elif has_file_to_upload and not uploaded_to_s3 and image_ok:
#In this case, an image was submitted by the student, but the image could not be uploaded to S3. Likely
#a config issue (development vs deployment). For now, just treat this as a "success"
# In this case, an image was submitted by the student, but the image could not be uploaded to S3. Likely
# a config issue (development vs deployment). For now, just treat this as a "success"
log.exception("Student AJAX post to combined open ended xmodule indicated that it contained an image, "
"but the image was not able to be uploaded to S3. This could indicate a config"
"issue with this deployment, but it could also indicate a problem with S3 or with the"
"student image itself.")
overall_success = True
elif not has_file_to_upload:
#If there is no file to upload, probably the student has embedded the link in the answer text
# If there is no file to upload, probably the student has embedded the link in the answer text
success, get_data['student_answer'] = self.check_for_url_in_text(get_data['student_answer'])
overall_success = success
#log.debug("Has file: {0} Uploaded: {1} Image Ok: {2}".format(has_file_to_upload, uploaded_to_s3, image_ok))
# log.debug("Has file: {0} Uploaded: {1} Image Ok: {2}".format(has_file_to_upload, uploaded_to_s3, image_ok))
return overall_success, get_data
......@@ -441,7 +442,7 @@ class OpenEndedChild(object):
success = False
allowed_to_submit = True
response = {}
#This is a student_facing_error
# This is a student_facing_error
error_string = ("You need to peer grade {0} more in order to make another submission. "
"You have graded {1}, and {2} are required. You have made {3} successful peer grading submissions.")
try:
......@@ -451,17 +452,17 @@ class OpenEndedChild(object):
student_sub_count = response['student_sub_count']
success = True
except:
#This is a dev_facing_error
# This is a dev_facing_error
log.error("Could not contact external open ended graders for location {0} and student {1}".format(
self.location_string, student_id))
#This is a student_facing_error
# This is a student_facing_error
error_message = "Could not contact the graders. Please notify course staff."
return success, allowed_to_submit, error_message
if count_graded >= count_required:
return success, allowed_to_submit, ""
else:
allowed_to_submit = False
#This is a student_facing_error
# This is a student_facing_error
error_message = error_string.format(count_required - count_graded, count_graded, count_required,
student_sub_count)
return success, allowed_to_submit, error_message
......
......@@ -15,6 +15,7 @@ from xmodule.fields import Date, StringyFloat, StringyInteger, StringyBoolean
from xmodule.open_ended_grading_classes.peer_grading_service import PeerGradingService, GradingServiceError, MockPeerGradingService
from open_ended_grading_classes import combined_open_ended_rubric
from django.utils.timezone import UTC
log = logging.getLogger(__name__)
......@@ -76,7 +77,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
def __init__(self, system, location, descriptor, model_data):
XModule.__init__(self, system, location, descriptor, model_data)
#We need to set the location here so the child modules can use it
# We need to set the location here so the child modules can use it
system.set('location', location)
self.system = system
if (self.system.open_ended_grading_interface):
......@@ -112,7 +113,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
if not self.ajax_url.endswith("/"):
self.ajax_url = self.ajax_url + "/"
#StringyInteger could return None, so keep this check.
# StringyInteger could return None, so keep this check.
if not isinstance(self.max_grade, int):
raise TypeError("max_grade needs to be an integer.")
......@@ -120,7 +121,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
return self._closed(self.timeinfo)
def _closed(self, timeinfo):
if timeinfo.close_date is not None and datetime.utcnow() > timeinfo.close_date:
if timeinfo.close_date is not None and datetime.now(UTC()) > timeinfo.close_date:
return True
return False
......@@ -166,9 +167,9 @@ class PeerGradingModule(PeerGradingFields, XModule):
}
if dispatch not in handlers:
#This is a dev_facing_error
# This is a dev_facing_error
log.error("Cannot find {0} in handlers in handle_ajax function for open_ended_module.py".format(dispatch))
#This is a dev_facing_error
# This is a dev_facing_error
return json.dumps({'error': 'Error handling action. Please try again.', 'success': False})
d = handlers[dispatch](get)
......@@ -187,7 +188,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
count_required = response['count_required']
success = True
except GradingServiceError:
#This is a dev_facing_error
# This is a dev_facing_error
log.exception("Error getting location data from controller for location {0}, student {1}"
.format(location, student_id))
......@@ -220,7 +221,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
count_graded = response['count_graded']
count_required = response['count_required']
if count_required > 0 and count_graded >= count_required:
#Ensures that once a student receives a final score for peer grading, that it does not change.
# Ensures that once a student receives a final score for peer grading, that it does not change.
self.student_data_for_location = response
if self.weight is not None:
......@@ -271,10 +272,10 @@ class PeerGradingModule(PeerGradingFields, XModule):
response = self.peer_gs.get_next_submission(location, grader_id)
return response
except GradingServiceError:
#This is a dev_facing_error
# This is a dev_facing_error
log.exception("Error getting next submission. server url: {0} location: {1}, grader_id: {2}"
.format(self.peer_gs.url, location, grader_id))
#This is a student_facing_error
# This is a student_facing_error
return {'success': False,
'error': EXTERNAL_GRADER_NO_CONTACT_ERROR}
......@@ -314,13 +315,13 @@ class PeerGradingModule(PeerGradingFields, XModule):
score, feedback, submission_key, rubric_scores, submission_flagged)
return response
except GradingServiceError:
#This is a dev_facing_error
# This is a dev_facing_error
log.exception("""Error saving grade to open ended grading service. server url: {0}, location: {1}, submission_id:{2},
submission_key: {3}, score: {4}"""
.format(self.peer_gs.url,
location, submission_id, submission_key, score)
)
#This is a student_facing_error
# This is a student_facing_error
return {
'success': False,
'error': EXTERNAL_GRADER_NO_CONTACT_ERROR
......@@ -356,10 +357,10 @@ class PeerGradingModule(PeerGradingFields, XModule):
response = self.peer_gs.is_student_calibrated(location, grader_id)
return response
except GradingServiceError:
#This is a dev_facing_error
# This is a dev_facing_error
log.exception("Error from open ended grading service. server url: {0}, grader_id: {0}, location: {1}"
.format(self.peer_gs.url, grader_id, location))
#This is a student_facing_error
# This is a student_facing_error
return {
'success': False,
'error': EXTERNAL_GRADER_NO_CONTACT_ERROR
......@@ -401,17 +402,17 @@ class PeerGradingModule(PeerGradingFields, XModule):
response = self.peer_gs.show_calibration_essay(location, grader_id)
return response
except GradingServiceError:
#This is a dev_facing_error
# This is a dev_facing_error
log.exception("Error from open ended grading service. server url: {0}, location: {0}"
.format(self.peer_gs.url, location))
#This is a student_facing_error
# This is a student_facing_error
return {'success': False,
'error': EXTERNAL_GRADER_NO_CONTACT_ERROR}
# if we can't parse the rubric into HTML,
except etree.XMLSyntaxError:
#This is a dev_facing_error
# This is a dev_facing_error
log.exception("Cannot parse rubric string.")
#This is a student_facing_error
# This is a student_facing_error
return {'success': False,
'error': 'Error displaying submission. Please notify course staff.'}
......@@ -455,11 +456,11 @@ class PeerGradingModule(PeerGradingFields, XModule):
response['actual_rubric'] = rubric_renderer.render_rubric(response['actual_rubric'])['html']
return response
except GradingServiceError:
#This is a dev_facing_error
# This is a dev_facing_error
log.exception(
"Error saving calibration grade, location: {0}, submission_key: {1}, grader_id: {2}".format(
location, submission_key, grader_id))
#This is a student_facing_error
# This is a student_facing_error
return self._err_response('There was an error saving your score. Please notify course staff.')
def peer_grading_closed(self):
......@@ -491,13 +492,13 @@ class PeerGradingModule(PeerGradingFields, XModule):
problem_list = problem_list_dict['problem_list']
except GradingServiceError:
#This is a student_facing_error
# This is a student_facing_error
error_text = EXTERNAL_GRADER_NO_CONTACT_ERROR
log.error(error_text)
success = False
# catch error if if the json loads fails
except ValueError:
#This is a student_facing_error
# This is a student_facing_error
error_text = "Could not get list of problems to peer grade. Please notify course staff."
log.error(error_text)
success = False
......@@ -557,8 +558,8 @@ class PeerGradingModule(PeerGradingFields, XModule):
'''
if get is None or get.get('location') is None:
if self.use_for_single_location not in TRUE_DICT:
#This is an error case, because it must be set to use a single location to be called without get parameters
#This is a dev_facing_error
# This is an error case, because it must be set to use a single location to be called without get parameters
# This is a dev_facing_error
log.error(
"Peer grading problem in peer_grading_module called with no get parameters, but use_for_single_location is False.")
return {'html': "", 'success': False}
......
......@@ -18,6 +18,7 @@ from xmodule.modulestore import Location
from django.http import QueryDict
from . import test_system
from pytz import UTC
class CapaFactory(object):
......@@ -126,7 +127,7 @@ class CapaFactory(object):
class CapaModuleTest(unittest.TestCase):
def setUp(self):
now = datetime.datetime.now()
now = datetime.datetime.now(UTC)
day_delta = datetime.timedelta(days=1)
self.yesterday_str = str(now - day_delta)
self.today_str = str(now)
......@@ -475,12 +476,12 @@ class CapaModuleTest(unittest.TestCase):
# Simulate that the problem is queued
with patch('capa.capa_problem.LoncapaProblem.is_queued') \
as mock_is_queued,\
as mock_is_queued, \
patch('capa.capa_problem.LoncapaProblem.get_recentmost_queuetime') \
as mock_get_queuetime:
mock_is_queued.return_value = True
mock_get_queuetime.return_value = datetime.datetime.now()
mock_get_queuetime.return_value = datetime.datetime.now(UTC)
get_request_dict = {CapaFactory.input_key(): '3.14'}
result = module.check_problem(get_request_dict)
......
import unittest
from time import strptime
import datetime
from fs.memoryfs import MemoryFS
......@@ -8,13 +7,13 @@ from mock import Mock, patch
from xmodule.modulestore.xml import ImportSystem, XMLModuleStore
import xmodule.course_module
from xmodule.util.date_utils import time_to_datetime
from django.utils.timezone import UTC
ORG = 'test_org'
COURSE = 'test_course'
NOW = strptime('2013-01-01T01:00:00', '%Y-%m-%dT%H:%M:00')
NOW = datetime.datetime.strptime('2013-01-01T01:00:00', '%Y-%m-%dT%H:%M:00').replace(tzinfo=UTC())
class DummySystem(ImportSystem):
......@@ -81,10 +80,10 @@ class IsNewCourseTestCase(unittest.TestCase):
Mock(wraps=datetime.datetime)
)
mocked_datetime = datetime_patcher.start()
mocked_datetime.utcnow.return_value = time_to_datetime(NOW)
mocked_datetime.now.return_value = NOW
self.addCleanup(datetime_patcher.stop)
@patch('xmodule.course_module.time.gmtime')
@patch('xmodule.course_module.datetime.now')
def test_sorting_score(self, gmtime_mock):
gmtime_mock.return_value = NOW
......@@ -125,7 +124,7 @@ class IsNewCourseTestCase(unittest.TestCase):
print "Comparing %s to %s" % (a, b)
assertion(a_score, b_score)
@patch('xmodule.course_module.time.gmtime')
@patch('xmodule.course_module.datetime.now')
def test_start_date_text(self, gmtime_mock):
gmtime_mock.return_value = NOW
......
......@@ -3,19 +3,12 @@
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'))
from pytz import UTC
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))
test_time = datetime.datetime(1992, 3, 12, 15, 3, 30, tzinfo=UTC)
assert_equals(
"Mar 12, 1992 at 15:03 UTC",
date_utils.get_default_time_display(test_time))
......@@ -26,10 +19,36 @@ def test_get_default_time_display():
"Mar 12, 1992 at 15:03",
date_utils.get_default_time_display(test_time, False))
def test_get_default_time_display_notz():
test_time = datetime.datetime(1992, 3, 12, 15, 3, 30)
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))
# pylint: disable=W0232
class NamelessTZ(datetime.tzinfo):
def utcoffset(self, _dt):
return datetime.timedelta(hours=-3)
def dst(self, _dt):
return datetime.timedelta(0)
def test_get_default_time_display_no_tzname():
assert_equals("", date_utils.get_default_time_display(None))
test_time = datetime.datetime(1992, 3, 12, 15, 3, 30, tzinfo=NamelessTZ())
assert_equals(
"Mar 12, 1992 at 15:03-0300",
date_utils.get_default_time_display(test_time))
assert_equals(
"Mar 12, 1992 at 15:03-0300",
date_utils.get_default_time_display(test_time, True))
assert_equals(
datetime.datetime(1992, 3, 12, 15, 3, 30),
date_utils.time_to_datetime(test_time))
"Mar 12, 1992 at 15:03",
date_utils.get_default_time_display(test_time, False))
"""Tests for classes defined in fields.py."""
import datetime
import unittest
from django.utils.timezone import UTC
from xmodule.fields import Date, StringyFloat, StringyInteger, StringyBoolean
import time
from django.utils.timezone import UTC
class DateTest(unittest.TestCase):
date = Date()
@staticmethod
def struct_to_datetime(struct_time):
return datetime.datetime(struct_time.tm_year, struct_time.tm_mon,
struct_time.tm_mday, struct_time.tm_hour,
struct_time.tm_min, struct_time.tm_sec, tzinfo=UTC())
def compare_dates(self, date1, date2, expected_delta):
dt1 = DateTest.struct_to_datetime(date1)
dt2 = DateTest.struct_to_datetime(date2)
self.assertEqual(dt1 - dt2, expected_delta, str(date1) + "-"
+ str(date2) + "!=" + str(expected_delta))
def compare_dates(self, dt1, dt2, expected_delta):
self.assertEqual(dt1 - dt2, expected_delta, str(dt1) + "-"
+ str(dt2) + "!=" + str(expected_delta))
def test_from_json(self):
'''Test conversion from iso compatible date strings to struct_time'''
......@@ -55,10 +46,10 @@ class DateTest(unittest.TestCase):
def test_old_due_date_format(self):
current = datetime.datetime.today()
self.assertEqual(
time.struct_time((current.year, 3, 12, 12, 0, 0, 1, 71, 0)),
datetime.datetime(current.year, 3, 12, 12, tzinfo=UTC()),
DateTest.date.from_json("March 12 12:00"))
self.assertEqual(
time.struct_time((current.year, 12, 4, 16, 30, 0, 2, 338, 0)),
datetime.datetime(current.year, 12, 4, 16, 30, tzinfo=UTC()),
DateTest.date.from_json("December 4 16:30"))
def test_to_json(self):
......@@ -67,7 +58,7 @@ class DateTest(unittest.TestCase):
'''
self.assertEqual(
DateTest.date.to_json(
time.strptime("2012-12-31T23:59:59Z", "%Y-%m-%dT%H:%M:%SZ")),
datetime.datetime.strptime("2012-12-31T23:59:59Z", "%Y-%m-%dT%H:%M:%SZ")),
"2012-12-31T23:59:59Z")
self.assertEqual(
DateTest.date.to_json(
......@@ -76,7 +67,7 @@ class DateTest(unittest.TestCase):
self.assertEqual(
DateTest.date.to_json(
DateTest.date.from_json("2012-12-31T23:00:01-01:00")),
"2013-01-01T00:00:01Z")
"2012-12-31T23:00:01-01:00")
class StringyIntegerTest(unittest.TestCase):
......
......@@ -13,6 +13,8 @@ from xmodule.modulestore.inheritance import compute_inherited_metadata
from xmodule.fields import Date
from .test_export import DATA_DIR
import datetime
from django.utils.timezone import UTC
ORG = 'test_org'
COURSE = 'test_course'
......@@ -40,7 +42,7 @@ class DummySystem(ImportSystem):
load_error_modules=load_error_modules,
)
def render_template(self, template, context):
def render_template(self, _template, _context):
raise Exception("Shouldn't be called")
......@@ -62,6 +64,7 @@ class BaseCourseTestCase(unittest.TestCase):
class ImportTestCase(BaseCourseTestCase):
date = Date()
def test_fallback(self):
'''Check that malformed xml loads as an ErrorDescriptor.'''
......@@ -145,15 +148,18 @@ class ImportTestCase(BaseCourseTestCase):
descriptor = system.process_xml(start_xml)
compute_inherited_metadata(descriptor)
# pylint: disable=W0212
print(descriptor, descriptor._model_data)
self.assertEqual(descriptor.lms.due, Date().from_json(v))
self.assertEqual(descriptor.lms.due, ImportTestCase.date.from_json(v))
# Check that the child inherits due correctly
child = descriptor.get_children()[0]
self.assertEqual(child.lms.due, Date().from_json(v))
self.assertEqual(child.lms.due, ImportTestCase.date.from_json(v))
self.assertEqual(child._inheritable_metadata, child._inherited_metadata)
self.assertEqual(2, len(child._inherited_metadata))
self.assertEqual('1970-01-01T00:00:00Z', child._inherited_metadata['start'])
self.assertLessEqual(ImportTestCase.date.from_json(
child._inherited_metadata['start']),
datetime.datetime.now(UTC()))
self.assertEqual(v, child._inherited_metadata['due'])
# Now export and check things
......@@ -209,9 +215,13 @@ class ImportTestCase(BaseCourseTestCase):
# Check that the child does not inherit a value for due
child = descriptor.get_children()[0]
self.assertEqual(child.lms.due, None)
# pylint: disable=W0212
self.assertEqual(child._inheritable_metadata, child._inherited_metadata)
self.assertEqual(1, len(child._inherited_metadata))
self.assertEqual('1970-01-01T00:00:00Z', child._inherited_metadata['start'])
# why do these tests look in the internal structure v just calling child.start?
self.assertLessEqual(
ImportTestCase.date.from_json(child._inherited_metadata['start']),
datetime.datetime.now(UTC()))
def test_metadata_override_default(self):
"""
......@@ -230,14 +240,17 @@ class ImportTestCase(BaseCourseTestCase):
</course>'''.format(due=course_due, org=ORG, course=COURSE, url_name=url_name)
descriptor = system.process_xml(start_xml)
child = descriptor.get_children()[0]
# pylint: disable=W0212
child._model_data['due'] = child_due
compute_inherited_metadata(descriptor)
self.assertEqual(descriptor.lms.due, Date().from_json(course_due))
self.assertEqual(child.lms.due, Date().from_json(child_due))
self.assertEqual(descriptor.lms.due, ImportTestCase.date.from_json(course_due))
self.assertEqual(child.lms.due, ImportTestCase.date.from_json(child_due))
# Test inherited metadata. Due does not appear here (because explicitly set on child).
self.assertEqual(1, len(child._inherited_metadata))
self.assertEqual('1970-01-01T00:00:00Z', child._inherited_metadata['start'])
self.assertLessEqual(
ImportTestCase.date.from_json(child._inherited_metadata['start']),
datetime.datetime.now(UTC()))
# Test inheritable metadata. This has the course inheritable value for due.
self.assertEqual(2, len(child._inheritable_metadata))
self.assertEqual(course_due, child._inheritable_metadata['due'])
......
from .timeparse import parse_timedelta
from xmodule.util.date_utils import time_to_datetime
import logging
log = logging.getLogger(__name__)
......@@ -17,7 +16,7 @@ class TimeInfo(object):
"""
def __init__(self, due_date, grace_period_string):
if due_date is not None:
self.display_due_date = time_to_datetime(due_date)
self.display_due_date = due_date
else:
self.display_due_date = None
......
"""
Helper functions for handling time in the format we like.
"""
import time
import re
from datetime import timedelta
from datetime import timedelta, datetime
TIME_FORMAT = "%Y-%m-%dT%H:%M"
......@@ -17,14 +16,14 @@ def parse_time(time_str):
Raises ValueError if the string is not in the right format.
"""
return time.strptime(time_str, TIME_FORMAT)
return datetime.strptime(time_str, TIME_FORMAT)
def stringify_time(time_struct):
def stringify_time(dt):
"""
Convert a time struct to a string
Convert a datetime struct to a string
"""
return time.strftime(TIME_FORMAT, time_struct)
return dt.isoformat()
def parse_timedelta(time_str):
"""
......
import time
import datetime
def get_default_time_display(time_struct, show_timezone=True):
def get_default_time_display(dt, show_timezone=True):
"""
Converts a time struct to a string representation. This is the default
Converts a datetime to a string representation. This is the default
representation used in Studio and LMS.
It is of the form "Apr 09, 2013 at 16:00" or "Apr 09, 2013 at 16:00 UTC",
depending on the value of show_timezone.
If None is passed in for time_struct, an empty string will be returned.
If None is passed in for dt, an empty string will be returned.
The default value of show_timezone is True.
"""
timezone = "" if time_struct is None or not show_timezone else " UTC"
return get_time_struct_display(time_struct, "%b %d, %Y at %H:%M") + timezone
def get_time_struct_display(time_struct, format):
"""
Converts a time struct to a string based on the given format.
If None is passed in, an empty string will be returned.
"""
return '' if time_struct is None else time.strftime(format, time_struct)
def time_to_datetime(time_struct):
"""
Convert a time struct to a datetime.
If None is passed in, None will be returned.
"""
return datetime.datetime(*time_struct[:6]) if time_struct else None
if dt is None:
return ""
timezone = ""
if dt is not None and show_timezone:
if dt.tzinfo is not None:
try:
timezone = " " + dt.tzinfo.tzname(dt)
except NotImplementedError:
timezone = dt.strftime('%z')
else:
timezone = " UTC"
return dt.strftime("%b %d, %Y at %H:%M") + timezone
<sequential>
<vertical filename="vertical_58" slug="vertical_58" graceperiod="1 day 12 hours 59 minutes 59 seconds" showanswer="attempted" rerandomize="never"/>
<vertical slug="vertical_66" graceperiod="1 day 12 hours 59 minutes 59 seconds" showanswer="attempted" rerandomize="never">
<problem filename="S1E3_AC_power" slug="S1E3_AC_power" name="S1E3: AC power"/>
<customtag tag="S1E3" slug="discuss_67" impl="discuss"/>
<vertical filename="vertical_58" slug="vertical_58"
graceperiod="1 day 12 hours 59 minutes 59 seconds" showanswer="attempted"
rerandomize="never" />
<vertical slug="vertical_66" graceperiod="1 day 12 hours 59 minutes 59 seconds"
showanswer="attempted" rerandomize="never">
<problem filename="S1E3_AC_power" slug="S1E3_AC_power"
name="S1E3: AC power" />
<customtag tag="S1E3" slug="discuss_67" impl="discuss" />
<!-- utf-8 characters acceptable, but not HTML entities -->
<html slug="html_68"> S1E4 has been removed…</html>
</vertical>
<vertical filename="vertical_89" slug="vertical_89" 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 filename="vertical_89" slug="vertical_89"
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">
......@@ -19,6 +29,6 @@
<!-- UTF-8 characters are acceptable… HTML entities are not -->
<h1>Inline content…</h1>
</html>
</vertical>
</vertical>
</randomize>
</sequential>
......@@ -16,6 +16,7 @@ from xmodule.x_module import XModule, XModuleDescriptor
from student.models import CourseEnrollmentAllowed
from courseware.masquerade import is_masquerading_as_student
from django.utils.timezone import UTC
DEBUG_ACCESS = False
......@@ -133,7 +134,7 @@ def _has_access_course_desc(user, course, action):
(staff can always enroll)
"""
now = time.gmtime()
now = datetime.now(UTC())
start = course.enrollment_start
end = course.enrollment_end
......@@ -242,7 +243,7 @@ def _has_access_descriptor(user, descriptor, action, course_context=None):
# Check start date
if descriptor.lms.start is not None:
now = time.gmtime()
now = datetime.now(UTC())
effective_start = _adjust_start_date_for_beta_testers(user, descriptor)
if now > effective_start:
# after start date, everyone can see it
......@@ -483,8 +484,7 @@ def _adjust_start_date_for_beta_testers(user, descriptor):
non-None start date.
Returns:
A time, in the same format as returned by time.gmtime(). Either the same as
start, or earlier for beta testers.
A datetime. Either the same as start, or earlier for beta testers.
NOTE: number of days to adjust should be cached to avoid looking it up thousands of
times per query.
......@@ -505,15 +505,11 @@ def _adjust_start_date_for_beta_testers(user, descriptor):
beta_group = course_beta_test_group_name(descriptor.location)
if beta_group in user_groups:
debug("Adjust start time: user in group %s", beta_group)
# time_structs don't support subtraction, so convert to datetimes,
# subtract, convert back.
# (fun fact: datetime(*a_time_struct[:6]) is the beautiful syntax for
# converting time_structs into datetimes)
start_as_datetime = datetime(*descriptor.lms.start[:6])
start_as_datetime = descriptor.lms.start
delta = timedelta(descriptor.lms.days_early_for_beta)
effective = start_as_datetime - delta
# ...and back to time_struct
return effective.timetuple()
return effective
return descriptor.lms.start
......
......@@ -12,6 +12,7 @@ from courseware.models import StudentModule, XModuleContentField, XModuleSetting
from courseware.models import XModuleStudentInfoField, XModuleStudentPrefsField
from xmodule.modulestore import Location
from pytz import UTC
location = partial(Location, 'i4x', 'edX', 'test_course', 'problem')
......@@ -28,8 +29,8 @@ class RegistrationFactory(StudentRegistrationFactory):
class UserFactory(StudentUserFactory):
email = 'robot@edx.org'
last_name = 'Tester'
last_login = datetime.now()
date_joined = datetime.now()
last_login = datetime.now(UTC)
date_joined = datetime.now(UTC)
class GroupFactory(StudentGroupFactory):
......
import unittest
import logging
import time
from mock import Mock, MagicMock, patch
from mock import Mock, patch
from django.conf import settings
from django.test import TestCase
from xmodule.course_module import CourseDescriptor
from xmodule.error_module import ErrorDescriptor
from xmodule.modulestore import Location
from xmodule.timeparse import parse_time
from xmodule.x_module import XModule, XModuleDescriptor
import courseware.access as access
from .factories import CourseEnrollmentAllowedFactory
import datetime
from django.utils.timezone import UTC
class AccessTestCase(TestCase):
......@@ -77,7 +71,7 @@ class AccessTestCase(TestCase):
# TODO: override DISABLE_START_DATES and test the start date branch of the method
u = Mock()
d = Mock()
d.start = time.gmtime(time.time() - 86400) # make sure the start time is in the past
d.start = datetime.datetime.now(UTC()) - datetime.timedelta(days=1) # make sure the start time is in the past
# Always returns true because DISABLE_START_DATES is set in test.py
self.assertTrue(access._has_access_descriptor(u, d, 'load'))
......@@ -85,8 +79,8 @@ class AccessTestCase(TestCase):
def test__has_access_course_desc_can_enroll(self):
u = Mock()
yesterday = time.gmtime(time.time() - 86400)
tomorrow = time.gmtime(time.time() + 86400)
yesterday = datetime.datetime.now(UTC()) - datetime.timedelta(days=1)
tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1)
c = Mock(enrollment_start=yesterday, enrollment_end=tomorrow)
# User can enroll if it is between the start and end dates
......
......@@ -3,7 +3,6 @@ Test for lms courseware app
'''
import logging
import json
import time
import random
from urlparse import urlsplit, urlunsplit
......@@ -30,6 +29,8 @@ from xmodule.modulestore.django import modulestore
from xmodule.modulestore import Location
from xmodule.modulestore.xml_importer import import_from_xml
from xmodule.modulestore.xml import XMLModuleStore
import datetime
from django.utils.timezone import UTC
log = logging.getLogger("mitx." + __name__)
......@@ -64,7 +65,7 @@ def mongo_store_config(data_dir):
'db': 'test_xmodule',
'collection': 'modulestore_%s' % uuid4().hex,
'fs_root': data_dir,
'render_template': 'mitxmako.shortcuts.render_to_string',
'render_template': 'mitxmako.shortcuts.render_to_string'
}
}
}
......@@ -603,9 +604,9 @@ class TestViewAuth(LoginEnrollmentTestCase):
"""Actually do the test, relying on settings to be right."""
# Make courses start in the future
tomorrow = time.time() + 24 * 3600
self.toy.lms.start = time.gmtime(tomorrow)
self.full.lms.start = time.gmtime(tomorrow)
tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1)
self.toy.lms.start = tomorrow
self.full.lms.start = tomorrow
self.assertFalse(self.toy.has_started())
self.assertFalse(self.full.has_started())
......@@ -728,18 +729,18 @@ class TestViewAuth(LoginEnrollmentTestCase):
"""Actually do the test, relying on settings to be right."""
# Make courses start in the future
tomorrow = time.time() + 24 * 3600
nextday = tomorrow + 24 * 3600
yesterday = time.time() - 24 * 3600
tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1)
nextday = tomorrow + datetime.timedelta(days=1)
yesterday = datetime.datetime.now(UTC()) - datetime.timedelta(days=1)
print "changing"
# toy course's enrollment period hasn't started
self.toy.enrollment_start = time.gmtime(tomorrow)
self.toy.enrollment_end = time.gmtime(nextday)
self.toy.enrollment_start = tomorrow
self.toy.enrollment_end = nextday
# full course's has
self.full.enrollment_start = time.gmtime(yesterday)
self.full.enrollment_end = time.gmtime(tomorrow)
self.full.enrollment_start = yesterday
self.full.enrollment_end = tomorrow
print "login"
# First, try with an enrolled student
......@@ -778,12 +779,10 @@ class TestViewAuth(LoginEnrollmentTestCase):
self.assertFalse(settings.MITX_FEATURES['DISABLE_START_DATES'])
# Make courses start in the future
tomorrow = time.time() + 24 * 3600
# nextday = tomorrow + 24 * 3600
# yesterday = time.time() - 24 * 3600
tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1)
# toy course's hasn't started
self.toy.lms.start = time.gmtime(tomorrow)
self.toy.lms.start = tomorrow
self.assertFalse(self.toy.has_started())
# but should be accessible for beta testers
......@@ -854,7 +853,7 @@ class TestSubmittingProblems(LoginEnrollmentTestCase):
modx_url = self.modx_url(problem_location, 'problem_check')
answer_key_prefix = 'input_i4x-edX-{}-problem-{}_'.format(self.course_slug, problem_url_name)
resp = self.client.post(modx_url,
{ (answer_key_prefix + k): v for k,v in responses.items() }
{ (answer_key_prefix + k): v for k, v in responses.items() }
)
return resp
......
import time
from collections import defaultdict
import logging
import time
import urllib
from datetime import datetime
from courseware.module_render import get_module
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.search import path_to_location
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.db import connection
......@@ -16,13 +11,12 @@ from django.http import HttpResponse
from django.utils import simplejson
from django_comment_common.models import Role
from django_comment_client.permissions import check_permissions_by_view
from xmodule.modulestore.exceptions import NoPathToItem
from mitxmako import middleware
import pystache_custom as pystache
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
from django.utils.timezone import UTC
log = logging.getLogger(__name__)
......@@ -100,7 +94,7 @@ def get_discussion_category_map(course):
def filter_unstarted_categories(category_map):
now = time.gmtime()
now = datetime.now(UTC())
result_map = {}
......@@ -220,12 +214,12 @@ def initialize_discussion_info(course):
for topic, entry in course.discussion_topics.items():
category_map['entries'][topic] = {"id": entry["id"],
"sort_key": entry.get("sort_key", topic),
"start_date": time.gmtime()}
"start_date": datetime.now(UTC())}
sort_map_entries(category_map)
_DISCUSSIONINFO[course.id]['id_map'] = discussion_id_map
_DISCUSSIONINFO[course.id]['category_map'] = category_map
_DISCUSSIONINFO[course.id]['timestamp'] = datetime.now()
_DISCUSSIONINFO[course.id]['timestamp'] = datetime.now(UTC())
class JsonResponse(HttpResponse):
......@@ -292,7 +286,7 @@ def get_ability(course_id, content, user):
'can_vote': check_permissions_by_view(user, course_id, content, "vote_for_thread" if content['type'] == 'thread' else "vote_for_comment"),
}
#TODO: RENAME
# TODO: RENAME
def get_annotated_content_info(course_id, content, user, user_info):
......@@ -310,7 +304,7 @@ def get_annotated_content_info(course_id, content, user, user_info):
'ability': get_ability(course_id, content, user),
}
#TODO: RENAME
# TODO: RENAME
def get_annotated_content_infos(course_id, thread, user, user_info):
......
......@@ -13,6 +13,7 @@ from foldit.models import PuzzleComplete, Score
from student.models import UserProfile, unique_id_for_user
from datetime import datetime, timedelta
from pytz import UTC
log = logging.getLogger(__name__)
......@@ -28,7 +29,7 @@ class FolditTestCase(TestCase):
self.user2 = User.objects.create_user('testuser2', 'test2@test.com', pwd)
self.unique_user_id = unique_id_for_user(self.user)
self.unique_user_id2 = unique_id_for_user(self.user2)
now = datetime.now()
now = datetime.now(UTC)
self.tomorrow = now + timedelta(days=1)
self.yesterday = now - timedelta(days=1)
......
......@@ -18,6 +18,7 @@ from django.core.management.base import BaseCommand
from student.models import UserProfile, Registration
from external_auth.models import ExternalAuthMap
from django.contrib.auth.models import User, Group
from pytz import UTC
class MyCompleter(object): # Custom completer
......@@ -124,7 +125,7 @@ class Command(BaseCommand):
external_credentials=json.dumps(credentials),
)
eamap.user = user
eamap.dtsignup = datetime.datetime.now()
eamap.dtsignup = datetime.datetime.now(UTC)
eamap.save()
print "User %s created successfully!" % user
......
......@@ -15,6 +15,7 @@ from scipy.optimize import curve_fit
from django.conf import settings
from django.db.models import Sum, Max
from psychometrics.models import *
from pytz import UTC
log = logging.getLogger("mitx.psychometrics")
......@@ -340,7 +341,7 @@ def make_psychometrics_data_update_handler(course_id, user, module_state_key):
checktimes = eval(pmd.checktimes) # update log of attempt timestamps
except:
checktimes = []
checktimes.append(datetime.datetime.now())
checktimes.append(datetime.datetime.now(UTC))
pmd.checktimes = checktimes
try:
pmd.save()
......
......@@ -11,6 +11,7 @@ from markdown import markdown
from .wiki_settings import *
from util.cache import cache
from pytz import UTC
class ShouldHaveExactlyOneRootSlug(Exception):
......@@ -265,7 +266,7 @@ class Revision(models.Model):
return
else:
import datetime
self.article.modified_on = datetime.datetime.now()
self.article.modified_on = datetime.datetime.now(UTC)
self.article.save()
# Increment counter according to previous revision
......
......@@ -24,7 +24,7 @@ modulestore_options = {
'db': 'test_xmodule',
'collection': 'acceptance_modulestore',
'fs_root': TEST_ROOT / "data",
'render_template': 'mitxmako.shortcuts.render_to_string',
'render_template': 'mitxmako.shortcuts.render_to_string'
}
MODULESTORE = {
......
......@@ -21,7 +21,7 @@ modulestore_options = {
'db': 'xmodule',
'collection': 'modulestore',
'fs_root': DATA_DIR,
'render_template': 'mitxmako.shortcuts.render_to_string',
'render_template': 'mitxmako.shortcuts.render_to_string'
}
MODULESTORE = {
......
......@@ -308,6 +308,7 @@ import monitoring.exceptions # noqa
# Change DEBUG/TEMPLATE_DEBUG in your environment settings files, not here
DEBUG = False
TEMPLATE_DEBUG = False
USE_TZ = True
# Site info
SITE_ID = 1
......
......@@ -19,7 +19,7 @@ MODULESTORE = {
'db': 'xmodule',
'collection': 'modulestore',
'fs_root': GITHUB_REPO_ROOT,
'render_template': 'mitxmako.shortcuts.render_to_string',
'render_template': 'mitxmako.shortcuts.render_to_string'
}
}
}
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