struct_time to datetime conversion.
This commit is contained in:
@@ -271,7 +271,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
)
|
||||
self.assertTrue(getattr(draft_problem, 'is_draft', False))
|
||||
|
||||
#now requery with depth
|
||||
# now requery with depth
|
||||
course = modulestore('draft').get_item(
|
||||
Location(['i4x', 'edX', 'simple', 'course', '2012_Fall', None]),
|
||||
depth=None
|
||||
@@ -539,7 +539,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
on_disk = loads(grading_policy.read())
|
||||
self.assertEqual(on_disk, course.grading_policy)
|
||||
|
||||
#check for policy.json
|
||||
# check for policy.json
|
||||
self.assertTrue(filesystem.exists('policy.json'))
|
||||
|
||||
# compare what's on disk to what we have in the course module
|
||||
@@ -990,7 +990,7 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
|
||||
def test_metadata_inheritance(self):
|
||||
module_store = modulestore('direct')
|
||||
import_from_xml(module_store, 'common/test/data/', ['full'])
|
||||
import_from_xml(module_store, 'common/test/data/', ['full'], verbose=True)
|
||||
|
||||
course = module_store.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None]))
|
||||
|
||||
|
||||
@@ -151,22 +151,16 @@ class CourseDetailsViewTest(CourseTestCase):
|
||||
self.assertEqual(details['intro_video'], encoded.get('intro_video', None), context + " intro_video not ==")
|
||||
self.assertEqual(details['effort'], encoded['effort'], context + " efforts not ==")
|
||||
|
||||
@staticmethod
|
||||
def struct_to_datetime(struct_time):
|
||||
return datetime.datetime(*struct_time[:6], tzinfo=UTC())
|
||||
|
||||
def compare_date_fields(self, details, encoded, context, field):
|
||||
if details[field] is not None:
|
||||
date = Date()
|
||||
if field in encoded and encoded[field] is not None:
|
||||
encoded_encoded = date.from_json(encoded[field])
|
||||
dt1 = CourseDetailsViewTest.struct_to_datetime(encoded_encoded)
|
||||
dt1 = date.from_json(encoded[field])
|
||||
|
||||
if isinstance(details[field], datetime.datetime):
|
||||
dt2 = details[field]
|
||||
else:
|
||||
details_encoded = date.from_json(details[field])
|
||||
dt2 = CourseDetailsViewTest.struct_to_datetime(details_encoded)
|
||||
dt2 = date.from_json(details[field])
|
||||
|
||||
expected_delta = datetime.timedelta(0)
|
||||
self.assertEqual(dt1 - dt2, expected_delta, str(dt1) + "!=" + str(dt2) + " at " + context)
|
||||
|
||||
@@ -62,7 +62,7 @@ def asset_index(request, org, course, name):
|
||||
asset_id = asset['_id']
|
||||
display_info = {}
|
||||
display_info['displayname'] = asset['displayname']
|
||||
display_info['uploadDate'] = get_default_time_display(asset['uploadDate'].timetuple())
|
||||
display_info['uploadDate'] = get_default_time_display(asset['uploadDate'])
|
||||
|
||||
asset_location = StaticContent.compute_location(asset_id['org'], asset_id['course'], asset_id['name'])
|
||||
display_info['url'] = StaticContent.get_url_path_from_location(asset_location)
|
||||
@@ -131,7 +131,7 @@ def upload_asset(request, org, course, coursename):
|
||||
readback = contentstore().find(content.location)
|
||||
|
||||
response_payload = {'displayname': content.name,
|
||||
'uploadDate': get_default_time_display(readback.last_modified_at.timetuple()),
|
||||
'uploadDate': get_default_time_display(readback.last_modified_at),
|
||||
'url': StaticContent.get_url_path_from_location(content.location),
|
||||
'thumb_url': StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_content is not None else None,
|
||||
'msg': 'Upload completed'
|
||||
@@ -231,7 +231,7 @@ def generate_export_course(request, org, course, name):
|
||||
logging.debug('root = {0}'.format(root_dir))
|
||||
|
||||
export_to_xml(modulestore('direct'), contentstore(), loc, root_dir, name, modulestore())
|
||||
#filename = root_dir / name + '.tar.gz'
|
||||
# filename = root_dir / name + '.tar.gz'
|
||||
|
||||
logging.debug('tar file being generated at {0}'.format(export_file.name))
|
||||
tar_file = tarfile.open(name=export_file.name, mode='w:gz')
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Views related to operations on course objects
|
||||
"""
|
||||
import json
|
||||
import time
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django_future.csrf import ensure_csrf_cookie
|
||||
@@ -32,6 +31,8 @@ from .component import OPEN_ENDED_COMPONENT_TYPES, \
|
||||
NOTE_COMPONENT_TYPES, ADVANCED_COMPONENT_POLICY_KEY
|
||||
|
||||
from django_comment_common.utils import seed_permissions_roles
|
||||
import datetime
|
||||
from django.utils.timezone import UTC
|
||||
|
||||
# TODO: should explicitly enumerate exports with __all__
|
||||
|
||||
@@ -130,7 +131,7 @@ def create_new_course(request):
|
||||
new_course.display_name = display_name
|
||||
|
||||
# set a default start date to now
|
||||
new_course.start = time.gmtime()
|
||||
new_course.start = datetime.datetime.now(UTC())
|
||||
|
||||
initialize_course_tabs(new_course)
|
||||
|
||||
@@ -357,49 +358,49 @@ def course_advanced_updates(request, org, course, name):
|
||||
# Whether or not to filter the tabs key out of the settings metadata
|
||||
filter_tabs = True
|
||||
|
||||
#Check to see if the user instantiated any advanced components. This is a hack
|
||||
#that does the following :
|
||||
# 1) adds/removes the open ended panel tab to a course automatically if the user
|
||||
# Check to see if the user instantiated any advanced components. This is a hack
|
||||
# that does the following :
|
||||
# 1) adds/removes the open ended panel tab to a course automatically if the user
|
||||
# has indicated that they want to edit the combinedopendended or peergrading module
|
||||
# 2) adds/removes the notes panel tab to a course automatically if the user has
|
||||
# indicated that they want the notes module enabled in their course
|
||||
# TODO refactor the above into distinct advanced policy settings
|
||||
if ADVANCED_COMPONENT_POLICY_KEY in request_body:
|
||||
#Get the course so that we can scrape current tabs
|
||||
# Get the course so that we can scrape current tabs
|
||||
course_module = modulestore().get_item(location)
|
||||
|
||||
#Maps tab types to components
|
||||
# Maps tab types to components
|
||||
tab_component_map = {
|
||||
'open_ended': OPEN_ENDED_COMPONENT_TYPES,
|
||||
'open_ended': OPEN_ENDED_COMPONENT_TYPES,
|
||||
'notes': NOTE_COMPONENT_TYPES,
|
||||
}
|
||||
|
||||
#Check to see if the user instantiated any notes or open ended components
|
||||
# Check to see if the user instantiated any notes or open ended components
|
||||
for tab_type in tab_component_map.keys():
|
||||
component_types = tab_component_map.get(tab_type)
|
||||
found_ac_type = False
|
||||
for ac_type in component_types:
|
||||
if ac_type in request_body[ADVANCED_COMPONENT_POLICY_KEY]:
|
||||
#Add tab to the course if needed
|
||||
# Add tab to the course if needed
|
||||
changed, new_tabs = add_extra_panel_tab(tab_type, course_module)
|
||||
#If a tab has been added to the course, then send the metadata along to CourseMetadata.update_from_json
|
||||
# If a tab has been added to the course, then send the metadata along to CourseMetadata.update_from_json
|
||||
if changed:
|
||||
course_module.tabs = new_tabs
|
||||
request_body.update({'tabs': new_tabs})
|
||||
#Indicate that tabs should not be filtered out of the metadata
|
||||
# Indicate that tabs should not be filtered out of the metadata
|
||||
filter_tabs = False
|
||||
#Set this flag to avoid the tab removal code below.
|
||||
# Set this flag to avoid the tab removal code below.
|
||||
found_ac_type = True
|
||||
break
|
||||
#If we did not find a module type in the advanced settings,
|
||||
# If we did not find a module type in the advanced settings,
|
||||
# we may need to remove the tab from the course.
|
||||
if not found_ac_type:
|
||||
#Remove tab from the course if needed
|
||||
# Remove tab from the course if needed
|
||||
changed, new_tabs = remove_extra_panel_tab(tab_type, course_module)
|
||||
if changed:
|
||||
course_module.tabs = new_tabs
|
||||
request_body.update({'tabs': new_tabs})
|
||||
#Indicate that tabs should *not* be filtered out of the metadata
|
||||
# Indicate that tabs should *not* be filtered out of the metadata
|
||||
filter_tabs = False
|
||||
|
||||
response_json = json.dumps(CourseMetadata.update_from_json(location,
|
||||
|
||||
@@ -3,26 +3,26 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from xmodule.modulestore.inheritance import own_metadata
|
||||
import json
|
||||
from json.encoder import JSONEncoder
|
||||
import time
|
||||
from contentstore.utils import get_modulestore
|
||||
from models.settings import course_grading
|
||||
from contentstore.utils import update_item
|
||||
from xmodule.fields import Date
|
||||
import re
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
|
||||
class CourseDetails(object):
|
||||
def __init__(self, location):
|
||||
self.course_location = location # a Location obj
|
||||
self.course_location = location # a Location obj
|
||||
self.start_date = None # 'start'
|
||||
self.end_date = None # 'end'
|
||||
self.end_date = None # 'end'
|
||||
self.enrollment_start = None
|
||||
self.enrollment_end = None
|
||||
self.syllabus = None # a pdf file asset
|
||||
self.overview = "" # html to render as the overview
|
||||
self.intro_video = None # a video pointer
|
||||
self.effort = None # int hours/week
|
||||
self.syllabus = None # a pdf file asset
|
||||
self.overview = "" # html to render as the overview
|
||||
self.intro_video = None # a video pointer
|
||||
self.effort = None # int hours/week
|
||||
|
||||
@classmethod
|
||||
def fetch(cls, course_location):
|
||||
@@ -73,9 +73,9 @@ class CourseDetails(object):
|
||||
"""
|
||||
Decode the json into CourseDetails and save any changed attrs to the db
|
||||
"""
|
||||
## TODO make it an error for this to be undefined & for it to not be retrievable from modulestore
|
||||
# # TODO make it an error for this to be undefined & for it to not be retrievable from modulestore
|
||||
course_location = jsondict['course_location']
|
||||
## Will probably want to cache the inflight courses because every blur generates an update
|
||||
# # Will probably want to cache the inflight courses because every blur generates an update
|
||||
descriptor = get_modulestore(course_location).get_item(course_location)
|
||||
|
||||
dirty = False
|
||||
@@ -181,7 +181,7 @@ class CourseSettingsEncoder(json.JSONEncoder):
|
||||
return obj.__dict__
|
||||
elif isinstance(obj, Location):
|
||||
return obj.dict()
|
||||
elif isinstance(obj, time.struct_time):
|
||||
elif isinstance(obj, datetime.datetime):
|
||||
return Date().to_json(obj)
|
||||
else:
|
||||
return JSONEncoder.default(self, obj)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<%inherit file="base.html" />
|
||||
<%!
|
||||
import logging
|
||||
from xmodule.util.date_utils import get_time_struct_display
|
||||
from xmodule.util.date_utils import get_default_time_display
|
||||
%>
|
||||
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
@@ -36,11 +36,15 @@
|
||||
<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')}"
|
||||
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')}"
|
||||
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>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<%inherit file="base.html" />
|
||||
<%!
|
||||
import logging
|
||||
from xmodule.util.date_utils import get_time_struct_display
|
||||
from xmodule.util import date_utils
|
||||
%>
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%block name="title">Course Outline</%block>
|
||||
@@ -154,14 +154,15 @@
|
||||
<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')
|
||||
start_date_str = section.lms.start.strftime('%m/%d/%Y')
|
||||
start_time_str = section.lms.start.strftime('%H:%M')
|
||||
%>
|
||||
%if section.lms.start is None:
|
||||
<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>
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import logging
|
||||
import time
|
||||
|
||||
from django.http import HttpResponse, Http404, HttpResponseNotModified
|
||||
from django.http import HttpResponse, HttpResponseNotModified
|
||||
|
||||
from xmodule.contentstore.django import contentstore
|
||||
from xmodule.contentstore.content import StaticContent, XASSET_LOCATION_TAG
|
||||
@@ -20,7 +17,7 @@ class StaticContentServer(object):
|
||||
# return a 'Bad Request' to browser as we have a malformed Location
|
||||
response = HttpResponse()
|
||||
response.status_code = 400
|
||||
return response
|
||||
return response
|
||||
|
||||
# first look in our cache so we don't have to round-trip to the DB
|
||||
content = get_cached_content(loc)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from optparse import make_option
|
||||
from time import strftime
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
@@ -128,8 +127,8 @@ class Command(BaseCommand):
|
||||
exam = CourseDescriptor.TestCenterExam(course_id, exam_name, exam_info)
|
||||
# update option values for date_first and date_last to use YYYY-MM-DD format
|
||||
# instead of YYYY-MM-DDTHH:MM
|
||||
our_options['eligibility_appointment_date_first'] = strftime("%Y-%m-%d", exam.first_eligible_appointment_date)
|
||||
our_options['eligibility_appointment_date_last'] = strftime("%Y-%m-%d", exam.last_eligible_appointment_date)
|
||||
our_options['eligibility_appointment_date_first'] = exam.first_eligible_appointment_date.strftime("%Y-%m-%d")
|
||||
our_options['eligibility_appointment_date_last'] = exam.last_eligible_appointment_date.strftime("%Y-%m-%d")
|
||||
|
||||
if exam is None:
|
||||
raise CommandError("Exam for course_id {} does not exist".format(course_id))
|
||||
|
||||
@@ -16,7 +16,6 @@ import json
|
||||
import logging
|
||||
import uuid
|
||||
from random import randint
|
||||
from time import strftime
|
||||
|
||||
|
||||
from django.conf import settings
|
||||
@@ -54,7 +53,7 @@ class UserProfile(models.Model):
|
||||
class Meta:
|
||||
db_table = "auth_userprofile"
|
||||
|
||||
## CRITICAL TODO/SECURITY
|
||||
# # CRITICAL TODO/SECURITY
|
||||
# Sanitize all fields.
|
||||
# This is not visible to other users, but could introduce holes later
|
||||
user = models.OneToOneField(User, unique=True, db_index=True, related_name='profile')
|
||||
@@ -429,8 +428,8 @@ class TestCenterRegistration(models.Model):
|
||||
registration.course_id = exam.course_id
|
||||
registration.accommodation_request = accommodation_request.strip()
|
||||
registration.exam_series_code = exam.exam_series_code
|
||||
registration.eligibility_appointment_date_first = strftime("%Y-%m-%d", exam.first_eligible_appointment_date)
|
||||
registration.eligibility_appointment_date_last = strftime("%Y-%m-%d", exam.last_eligible_appointment_date)
|
||||
registration.eligibility_appointment_date_first = exam.first_eligible_appointment_date.strftime("%Y-%m-%d")
|
||||
registration.eligibility_appointment_date_last = exam.last_eligible_appointment_date.strftime("%Y-%m-%d")
|
||||
registration.client_authorization_id = cls._create_client_authorization_id()
|
||||
# accommodation_code remains blank for now, along with Pearson confirmation information
|
||||
return registration
|
||||
@@ -598,7 +597,7 @@ def unique_id_for_user(user):
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
## TODO: Should be renamed to generic UserGroup, and possibly
|
||||
# # TODO: Should be renamed to generic UserGroup, and possibly
|
||||
# Given an optional field for type of group
|
||||
class UserTestGroup(models.Model):
|
||||
users = models.ManyToManyField(User, db_index=True)
|
||||
@@ -626,7 +625,7 @@ class Registration(models.Model):
|
||||
def activate(self):
|
||||
self.user.is_active = True
|
||||
self.user.save()
|
||||
#self.delete()
|
||||
# self.delete()
|
||||
|
||||
|
||||
class PendingNameChange(models.Model):
|
||||
@@ -648,7 +647,7 @@ class CourseEnrollment(models.Model):
|
||||
created = models.DateTimeField(auto_now_add=True, null=True, db_index=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = (('user', 'course_id'), )
|
||||
unique_together = (('user', 'course_id'),)
|
||||
|
||||
def __unicode__(self):
|
||||
return "[CourseEnrollment] %s: %s (%s)" % (self.user, self.course_id, self.created)
|
||||
@@ -667,12 +666,12 @@ class CourseEnrollmentAllowed(models.Model):
|
||||
created = models.DateTimeField(auto_now_add=True, null=True, db_index=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = (('email', 'course_id'), )
|
||||
unique_together = (('email', 'course_id'),)
|
||||
|
||||
def __unicode__(self):
|
||||
return "[CourseEnrollmentAllowed] %s: %s (%s)" % (self.email, self.course_id, self.created)
|
||||
|
||||
#cache_relation(User.profile)
|
||||
# cache_relation(User.profile)
|
||||
|
||||
#### Helper methods for use from python manage.py shell and other classes.
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import re
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
import static_replace
|
||||
|
||||
from django.conf import settings
|
||||
@@ -9,6 +8,8 @@ from functools import wraps
|
||||
from mitxmako.shortcuts import render_to_string
|
||||
from xmodule.seq_module import SequenceModule
|
||||
from xmodule.vertical_module import VerticalModule
|
||||
import datetime
|
||||
from django.utils.timezone import UTC
|
||||
|
||||
log = logging.getLogger("mitx.xmodule_modifiers")
|
||||
|
||||
@@ -83,7 +84,7 @@ def grade_histogram(module_id):
|
||||
cursor.execute(q, [module_id])
|
||||
|
||||
grades = list(cursor.fetchall())
|
||||
grades.sort(key=lambda x: x[0]) # Add ORDER BY to sql query?
|
||||
grades.sort(key=lambda x: x[0]) # Add ORDER BY to sql query?
|
||||
if len(grades) >= 1 and grades[0][0] is None:
|
||||
return []
|
||||
return grades
|
||||
@@ -101,7 +102,7 @@ def add_histogram(get_html, module, user):
|
||||
@wraps(get_html)
|
||||
def _get_html():
|
||||
|
||||
if type(module) in [SequenceModule, VerticalModule]: # TODO: make this more general, eg use an XModule attribute instead
|
||||
if type(module) in [SequenceModule, VerticalModule]: # TODO: make this more general, eg use an XModule attribute instead
|
||||
return get_html()
|
||||
|
||||
module_id = module.id
|
||||
@@ -132,7 +133,7 @@ def add_histogram(get_html, module, user):
|
||||
|
||||
# useful to indicate to staff if problem has been released or not
|
||||
# TODO (ichuang): use _has_access_descriptor.can_load in lms.courseware.access, instead of now>mstart comparison here
|
||||
now = time.gmtime()
|
||||
now = datetime.datetime.now(UTC())
|
||||
is_released = "unknown"
|
||||
mstart = module.descriptor.lms.start
|
||||
|
||||
|
||||
@@ -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? '''
|
||||
|
||||
@@ -4,7 +4,6 @@ from math import exp
|
||||
from lxml import etree
|
||||
from path import path # NOTE (THK): Only used for detecting presence of syllabus
|
||||
import requests
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
import dateutil.parser
|
||||
@@ -14,11 +13,11 @@ from xmodule.seq_module import SequenceDescriptor, SequenceModule
|
||||
from xmodule.timeparse import parse_time
|
||||
from xmodule.util.decorators import lazyproperty
|
||||
from xmodule.graders import grader_from_conf
|
||||
from xmodule.util.date_utils import time_to_datetime
|
||||
import json
|
||||
|
||||
from xblock.core import Scope, List, String, Object, Boolean
|
||||
from .fields import Date
|
||||
from django.utils.timezone import UTC
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -219,8 +218,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
|
||||
msg = None
|
||||
if self.start is None:
|
||||
msg = "Course loaded without a valid start date. id = %s" % self.id
|
||||
# hack it -- start in 1970
|
||||
self.start = time.gmtime(0)
|
||||
self.start = datetime.now(UTC())
|
||||
log.critical(msg)
|
||||
self.system.error_tracker(msg)
|
||||
|
||||
@@ -392,7 +390,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
|
||||
textbook_xml_object.set('book_url', textbook.book_url)
|
||||
|
||||
xml_object.append(textbook_xml_object)
|
||||
|
||||
|
||||
return xml_object
|
||||
|
||||
def has_ended(self):
|
||||
@@ -403,10 +401,10 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
|
||||
if self.end is None:
|
||||
return False
|
||||
|
||||
return time.gmtime() > self.end
|
||||
return datetime.now(UTC()) > self.end
|
||||
|
||||
def has_started(self):
|
||||
return time.gmtime() > self.start
|
||||
return datetime.now(UTC()) > self.start
|
||||
|
||||
@property
|
||||
def grader(self):
|
||||
@@ -547,14 +545,16 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
|
||||
|
||||
announcement = self.announcement
|
||||
if announcement is not None:
|
||||
announcement = time_to_datetime(announcement)
|
||||
announcement = announcement
|
||||
|
||||
try:
|
||||
start = dateutil.parser.parse(self.advertised_start)
|
||||
if start.tzinfo is None:
|
||||
start = start.replace(tzinfo=UTC())
|
||||
except (ValueError, AttributeError):
|
||||
start = time_to_datetime(self.start)
|
||||
start = self.start
|
||||
|
||||
now = datetime.utcnow()
|
||||
now = datetime.now(UTC())
|
||||
|
||||
return announcement, start, now
|
||||
|
||||
@@ -656,7 +656,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
|
||||
elif self.advertised_start is None and self.start is None:
|
||||
return 'TBD'
|
||||
else:
|
||||
return time.strftime("%b %d, %Y", self.advertised_start or self.start)
|
||||
return (self.advertised_start or self.start).strftime("%b %d, %Y")
|
||||
|
||||
@property
|
||||
def end_date_text(self):
|
||||
@@ -665,7 +665,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
|
||||
|
||||
If the course does not have an end date set (course.end is None), an empty string will be returned.
|
||||
"""
|
||||
return '' if self.end is None else time.strftime("%b %d, %Y", self.end)
|
||||
return '' if self.end is None else self.end.strftime("%b %d, %Y")
|
||||
|
||||
@property
|
||||
def forum_posts_allowed(self):
|
||||
@@ -673,7 +673,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
|
||||
blackout_periods = [(parse_time(start), parse_time(end))
|
||||
for start, end
|
||||
in self.discussion_blackouts]
|
||||
now = time.gmtime()
|
||||
now = datetime.now(UTC())
|
||||
for start, end in blackout_periods:
|
||||
if start <= now <= end:
|
||||
return False
|
||||
@@ -699,7 +699,8 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
|
||||
self.last_eligible_appointment_date = self._try_parse_time('Last_Eligible_Appointment_Date') # or self.first_eligible_appointment_date
|
||||
if self.last_eligible_appointment_date is None:
|
||||
raise ValueError("Last appointment date must be specified")
|
||||
self.registration_start_date = self._try_parse_time('Registration_Start_Date') or time.gmtime(0)
|
||||
self.registration_start_date = (self._try_parse_time('Registration_Start_Date') or
|
||||
datetime.utcfromtimestamp(0))
|
||||
self.registration_end_date = self._try_parse_time('Registration_End_Date') or self.last_eligible_appointment_date
|
||||
# do validation within the exam info:
|
||||
if self.registration_start_date > self.registration_end_date:
|
||||
@@ -725,32 +726,32 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
|
||||
return None
|
||||
|
||||
def has_started(self):
|
||||
return time.gmtime() > self.first_eligible_appointment_date
|
||||
return datetime.now(UTC()) > self.first_eligible_appointment_date
|
||||
|
||||
def has_ended(self):
|
||||
return time.gmtime() > self.last_eligible_appointment_date
|
||||
return datetime.now(UTC()) > self.last_eligible_appointment_date
|
||||
|
||||
def has_started_registration(self):
|
||||
return time.gmtime() > self.registration_start_date
|
||||
return datetime.now(UTC()) > self.registration_start_date
|
||||
|
||||
def has_ended_registration(self):
|
||||
return time.gmtime() > self.registration_end_date
|
||||
return datetime.now(UTC()) > self.registration_end_date
|
||||
|
||||
def is_registering(self):
|
||||
now = time.gmtime()
|
||||
now = datetime.now(UTC())
|
||||
return now >= self.registration_start_date and now <= self.registration_end_date
|
||||
|
||||
@property
|
||||
def first_eligible_appointment_date_text(self):
|
||||
return time.strftime("%b %d, %Y", self.first_eligible_appointment_date)
|
||||
return datetime.strftime("%b %d, %Y", self.first_eligible_appointment_date)
|
||||
|
||||
@property
|
||||
def last_eligible_appointment_date_text(self):
|
||||
return time.strftime("%b %d, %Y", self.last_eligible_appointment_date)
|
||||
return datetime.strftime("%b %d, %Y", self.last_eligible_appointment_date)
|
||||
|
||||
@property
|
||||
def registration_end_date_text(self):
|
||||
return time.strftime("%b %d, %Y at %H:%M UTC", self.registration_end_date)
|
||||
return datetime.strftime("%b %d, %Y at %H:%M UTC", self.registration_end_date)
|
||||
|
||||
@property
|
||||
def current_test_center_exam(self):
|
||||
|
||||
@@ -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)?)?$')
|
||||
@@ -74,7 +82,7 @@ class Timedelta(ModelType):
|
||||
for (name, param) in parts.iteritems():
|
||||
if param:
|
||||
time_params[name] = int(param)
|
||||
return timedelta(**time_params)
|
||||
return datetime.timedelta(**time_params)
|
||||
|
||||
def to_json(self, value):
|
||||
values = []
|
||||
@@ -93,7 +101,7 @@ class StringyInteger(Integer):
|
||||
def from_json(self, value):
|
||||
try:
|
||||
return int(value)
|
||||
except:
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -52,7 +52,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
|
||||
|
||||
xmlstore: the XMLModuleStore to store the loaded modules in
|
||||
"""
|
||||
self.unnamed = defaultdict(int) # category -> num of new url_names for that category
|
||||
self.unnamed = defaultdict(int) # category -> num of new url_names for that category
|
||||
self.used_names = defaultdict(set) # category -> set of used url_names
|
||||
self.org, self.course, self.url_name = course_id.split('/')
|
||||
# cdodge: adding the course_id as passed in for later reference rather than having to recomine the org/course/url_name
|
||||
@@ -124,7 +124,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
|
||||
else:
|
||||
# TODO (vshnayder): We may want to enable this once course repos are cleaned up.
|
||||
# (or we may want to give up on the requirement for non-state-relevant issues...)
|
||||
#error_tracker("WARNING: no name specified for module. xml='{0}...'".format(xml[:100]))
|
||||
# error_tracker("WARNING: no name specified for module. xml='{0}...'".format(xml[:100]))
|
||||
pass
|
||||
|
||||
# Make sure everything is unique
|
||||
@@ -447,7 +447,7 @@ class XMLModuleStore(ModuleStoreBase):
|
||||
def load_extra_content(self, system, course_descriptor, category, base_dir, course_dir, url_name):
|
||||
self._load_extra_content(system, course_descriptor, category, base_dir, course_dir)
|
||||
|
||||
# then look in a override folder based on the course run
|
||||
# then look in a override folder based on the course run
|
||||
if os.path.isdir(base_dir / url_name):
|
||||
self._load_extra_content(system, course_descriptor, category, base_dir / url_name, course_dir)
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import unittest
|
||||
from time import strptime
|
||||
import datetime
|
||||
|
||||
from fs.memoryfs import MemoryFS
|
||||
@@ -8,13 +7,13 @@ from mock import Mock, patch
|
||||
|
||||
from xmodule.modulestore.xml import ImportSystem, XMLModuleStore
|
||||
import xmodule.course_module
|
||||
from xmodule.util.date_utils import time_to_datetime
|
||||
from django.utils.timezone import UTC
|
||||
|
||||
|
||||
ORG = 'test_org'
|
||||
COURSE = 'test_course'
|
||||
|
||||
NOW = strptime('2013-01-01T01:00:00', '%Y-%m-%dT%H:%M:00')
|
||||
NOW = datetime.datetime.strptime('2013-01-01T01:00:00', '%Y-%m-%dT%H:%M:00').replace(tzinfo=UTC())
|
||||
|
||||
|
||||
class DummySystem(ImportSystem):
|
||||
@@ -81,10 +80,10 @@ class IsNewCourseTestCase(unittest.TestCase):
|
||||
Mock(wraps=datetime.datetime)
|
||||
)
|
||||
mocked_datetime = datetime_patcher.start()
|
||||
mocked_datetime.utcnow.return_value = time_to_datetime(NOW)
|
||||
mocked_datetime.now.return_value = NOW
|
||||
self.addCleanup(datetime_patcher.stop)
|
||||
|
||||
@patch('xmodule.course_module.time.gmtime')
|
||||
@patch('xmodule.course_module.datetime.now')
|
||||
def test_sorting_score(self, gmtime_mock):
|
||||
gmtime_mock.return_value = NOW
|
||||
|
||||
@@ -125,7 +124,7 @@ class IsNewCourseTestCase(unittest.TestCase):
|
||||
print "Comparing %s to %s" % (a, b)
|
||||
assertion(a_score, b_score)
|
||||
|
||||
@patch('xmodule.course_module.time.gmtime')
|
||||
@patch('xmodule.course_module.datetime.now')
|
||||
def test_start_date_text(self, gmtime_mock):
|
||||
gmtime_mock.return_value = NOW
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
# Tests for xmodule.util.date_utils
|
||||
|
||||
from nose.tools import assert_equals
|
||||
from xmodule.util import date_utils
|
||||
import datetime
|
||||
import time
|
||||
|
||||
|
||||
def test_get_time_struct_display():
|
||||
assert_equals("", date_utils.get_time_struct_display(None, ""))
|
||||
test_time = time.struct_time((1992, 3, 12, 15, 3, 30, 1, 71, 0))
|
||||
assert_equals("03/12/1992", date_utils.get_time_struct_display(test_time, '%m/%d/%Y'))
|
||||
assert_equals("15:03", date_utils.get_time_struct_display(test_time, '%H:%M'))
|
||||
|
||||
|
||||
def test_get_default_time_display():
|
||||
assert_equals("", date_utils.get_default_time_display(None))
|
||||
test_time = time.struct_time((1992, 3, 12, 15, 3, 30, 1, 71, 0))
|
||||
assert_equals(
|
||||
"Mar 12, 1992 at 15:03 UTC",
|
||||
date_utils.get_default_time_display(test_time))
|
||||
assert_equals(
|
||||
"Mar 12, 1992 at 15:03 UTC",
|
||||
date_utils.get_default_time_display(test_time, True))
|
||||
assert_equals(
|
||||
"Mar 12, 1992 at 15:03",
|
||||
date_utils.get_default_time_display(test_time, False))
|
||||
|
||||
|
||||
def test_time_to_datetime():
|
||||
assert_equals(None, date_utils.time_to_datetime(None))
|
||||
test_time = time.struct_time((1992, 3, 12, 15, 3, 30, 1, 71, 0))
|
||||
assert_equals(
|
||||
datetime.datetime(1992, 3, 12, 15, 3, 30),
|
||||
date_utils.time_to_datetime(test_time))
|
||||
@@ -1,24 +1,15 @@
|
||||
"""Tests for classes defined in fields.py."""
|
||||
import datetime
|
||||
import unittest
|
||||
from django.utils.timezone import UTC
|
||||
from xmodule.fields import Date, StringyFloat, StringyInteger, StringyBoolean
|
||||
import time
|
||||
from django.utils.timezone import UTC
|
||||
|
||||
class DateTest(unittest.TestCase):
|
||||
date = Date()
|
||||
|
||||
@staticmethod
|
||||
def struct_to_datetime(struct_time):
|
||||
return datetime.datetime(struct_time.tm_year, struct_time.tm_mon,
|
||||
struct_time.tm_mday, struct_time.tm_hour,
|
||||
struct_time.tm_min, struct_time.tm_sec, tzinfo=UTC())
|
||||
|
||||
def compare_dates(self, date1, date2, expected_delta):
|
||||
dt1 = DateTest.struct_to_datetime(date1)
|
||||
dt2 = DateTest.struct_to_datetime(date2)
|
||||
self.assertEqual(dt1 - dt2, expected_delta, str(date1) + "-"
|
||||
+ str(date2) + "!=" + str(expected_delta))
|
||||
def compare_dates(self, dt1, dt2, expected_delta):
|
||||
self.assertEqual(dt1 - dt2, expected_delta, str(dt1) + "-"
|
||||
+ str(dt2) + "!=" + str(expected_delta))
|
||||
|
||||
def test_from_json(self):
|
||||
'''Test conversion from iso compatible date strings to struct_time'''
|
||||
@@ -55,10 +46,10 @@ class DateTest(unittest.TestCase):
|
||||
def test_old_due_date_format(self):
|
||||
current = datetime.datetime.today()
|
||||
self.assertEqual(
|
||||
time.struct_time((current.year, 3, 12, 12, 0, 0, 1, 71, 0)),
|
||||
datetime.datetime(current.year, 3, 12, 12, tzinfo=UTC()),
|
||||
DateTest.date.from_json("March 12 12:00"))
|
||||
self.assertEqual(
|
||||
time.struct_time((current.year, 12, 4, 16, 30, 0, 2, 338, 0)),
|
||||
datetime.datetime(current.year, 12, 4, 16, 30, tzinfo=UTC()),
|
||||
DateTest.date.from_json("December 4 16:30"))
|
||||
|
||||
def test_to_json(self):
|
||||
@@ -67,7 +58,7 @@ class DateTest(unittest.TestCase):
|
||||
'''
|
||||
self.assertEqual(
|
||||
DateTest.date.to_json(
|
||||
time.strptime("2012-12-31T23:59:59Z", "%Y-%m-%dT%H:%M:%SZ")),
|
||||
datetime.datetime.strptime("2012-12-31T23:59:59Z", "%Y-%m-%dT%H:%M:%SZ")),
|
||||
"2012-12-31T23:59:59Z")
|
||||
self.assertEqual(
|
||||
DateTest.date.to_json(
|
||||
@@ -76,7 +67,7 @@ class DateTest(unittest.TestCase):
|
||||
self.assertEqual(
|
||||
DateTest.date.to_json(
|
||||
DateTest.date.from_json("2012-12-31T23:00:01-01:00")),
|
||||
"2013-01-01T00:00:01Z")
|
||||
"2012-12-31T23:00:01-01:00")
|
||||
|
||||
|
||||
class StringyIntegerTest(unittest.TestCase):
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from .timeparse import parse_timedelta
|
||||
from xmodule.util.date_utils import time_to_datetime
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -17,7 +16,7 @@ class TimeInfo(object):
|
||||
"""
|
||||
def __init__(self, due_date, grace_period_string):
|
||||
if due_date is not None:
|
||||
self.display_due_date = time_to_datetime(due_date)
|
||||
self.display_due_date = due_date
|
||||
|
||||
else:
|
||||
self.display_due_date = None
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
"""
|
||||
Helper functions for handling time in the format we like.
|
||||
"""
|
||||
import time
|
||||
import re
|
||||
from datetime import timedelta
|
||||
from datetime import timedelta, datetime
|
||||
|
||||
TIME_FORMAT = "%Y-%m-%dT%H:%M"
|
||||
|
||||
@@ -17,14 +16,14 @@ def parse_time(time_str):
|
||||
|
||||
Raises ValueError if the string is not in the right format.
|
||||
"""
|
||||
return time.strptime(time_str, TIME_FORMAT)
|
||||
return datetime.strptime(time_str, TIME_FORMAT)
|
||||
|
||||
|
||||
def stringify_time(time_struct):
|
||||
def stringify_time(dt):
|
||||
"""
|
||||
Convert a time struct to a string
|
||||
Convert a datetime struct to a string
|
||||
"""
|
||||
return time.strftime(TIME_FORMAT, time_struct)
|
||||
return dt.isoformat()
|
||||
|
||||
def parse_timedelta(time_str):
|
||||
"""
|
||||
|
||||
@@ -1,34 +1,20 @@
|
||||
import time
|
||||
import datetime
|
||||
|
||||
|
||||
def get_default_time_display(time_struct, show_timezone=True):
|
||||
def get_default_time_display(dt, show_timezone=True):
|
||||
"""
|
||||
Converts a time struct to a string representation. This is the default
|
||||
Converts a datetime to a string representation. This is the default
|
||||
representation used in Studio and LMS.
|
||||
It is of the form "Apr 09, 2013 at 16:00" or "Apr 09, 2013 at 16:00 UTC",
|
||||
depending on the value of show_timezone.
|
||||
|
||||
If None is passed in for time_struct, an empty string will be returned.
|
||||
If None is passed in for dt, an empty string will be returned.
|
||||
The default value of show_timezone is True.
|
||||
"""
|
||||
timezone = "" if time_struct is None or not show_timezone else " UTC"
|
||||
return get_time_struct_display(time_struct, "%b %d, %Y at %H:%M") + timezone
|
||||
|
||||
|
||||
def get_time_struct_display(time_struct, format):
|
||||
"""
|
||||
Converts a time struct to a string based on the given format.
|
||||
|
||||
If None is passed in, an empty string will be returned.
|
||||
"""
|
||||
return '' if time_struct is None else time.strftime(format, time_struct)
|
||||
|
||||
|
||||
def time_to_datetime(time_struct):
|
||||
"""
|
||||
Convert a time struct to a datetime.
|
||||
|
||||
If None is passed in, None will be returned.
|
||||
"""
|
||||
return datetime.datetime(*time_struct[:6]) if time_struct else None
|
||||
timezone = ""
|
||||
if dt is not None and show_timezone:
|
||||
if dt.tzinfo is not None:
|
||||
try:
|
||||
timezone = dt.tzinfo.tzname(dt)
|
||||
except NotImplementedError:
|
||||
timezone = " UTC"
|
||||
else:
|
||||
timezone = " UTC"
|
||||
return dt.strftime("%b %d, %Y at %H:%M") + timezone
|
||||
|
||||
@@ -1,24 +1,34 @@
|
||||
<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"/>
|
||||
<!-- 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>
|
||||
<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>
|
||||
|
||||
<randomize url_name="PS1_Q4" display_name="Problem 4: Read a Molecule">
|
||||
<vertical>
|
||||
<html slug="html_900">
|
||||
<!-- UTF-8 characters are acceptable… HTML entities are not -->
|
||||
<h1>Inline content…</h1>
|
||||
</html>
|
||||
</vertical>
|
||||
</randomize>
|
||||
<randomize url_name="PS1_Q4" display_name="Problem 4: Read a Molecule">
|
||||
<vertical>
|
||||
<html slug="html_900">
|
||||
<!-- UTF-8 characters are acceptable… HTML entities are not -->
|
||||
<h1>Inline content…</h1>
|
||||
</html>
|
||||
</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
|
||||
@@ -365,7 +366,7 @@ def _course_org_staff_group_name(location, course_context=None):
|
||||
|
||||
|
||||
def group_names_for(role, location, course_context=None):
|
||||
"""Returns the group names for a given role with this location. Plural
|
||||
"""Returns the group names for a given role with this location. Plural
|
||||
because it will return both the name we expect now as well as the legacy
|
||||
group name we support for backwards compatibility. This should not check
|
||||
the DB for existence of a group (like some of its callers do) because that's
|
||||
@@ -483,8 +484,7 @@ def _adjust_start_date_for_beta_testers(user, descriptor):
|
||||
non-None start date.
|
||||
|
||||
Returns:
|
||||
A time, in the same format as returned by time.gmtime(). Either the same as
|
||||
start, or earlier for beta testers.
|
||||
A datetime. Either the same as start, or earlier for beta testers.
|
||||
|
||||
NOTE: number of days to adjust should be cached to avoid looking it up thousands of
|
||||
times per query.
|
||||
@@ -505,15 +505,11 @@ def _adjust_start_date_for_beta_testers(user, descriptor):
|
||||
beta_group = course_beta_test_group_name(descriptor.location)
|
||||
if beta_group in user_groups:
|
||||
debug("Adjust start time: user in group %s", beta_group)
|
||||
# time_structs don't support subtraction, so convert to datetimes,
|
||||
# subtract, convert back.
|
||||
# (fun fact: datetime(*a_time_struct[:6]) is the beautiful syntax for
|
||||
# converting time_structs into datetimes)
|
||||
start_as_datetime = datetime(*descriptor.lms.start[:6])
|
||||
start_as_datetime = descriptor.lms.start
|
||||
delta = timedelta(descriptor.lms.days_early_for_beta)
|
||||
effective = start_as_datetime - delta
|
||||
# ...and back to time_struct
|
||||
return effective.timetuple()
|
||||
return effective
|
||||
|
||||
return descriptor.lms.start
|
||||
|
||||
@@ -564,7 +560,7 @@ def _has_access_to_location(user, location, access_level, course_context):
|
||||
return True
|
||||
debug("Deny: user not in groups %s", staff_groups)
|
||||
|
||||
if access_level == 'instructor' or access_level == 'staff': # instructors get staff privileges
|
||||
if access_level == 'instructor' or access_level == 'staff': # instructors get staff privileges
|
||||
instructor_groups = group_names_for_instructor(location, course_context) + \
|
||||
[_course_org_instructor_group_name(location, course_context)]
|
||||
for instructor_group in instructor_groups:
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
import unittest
|
||||
import logging
|
||||
import time
|
||||
from mock import Mock, MagicMock, patch
|
||||
from mock import Mock, patch
|
||||
|
||||
from django.conf import settings
|
||||
from django.test import TestCase
|
||||
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
from xmodule.error_module import ErrorDescriptor
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.timeparse import parse_time
|
||||
from xmodule.x_module import XModule, XModuleDescriptor
|
||||
import courseware.access as access
|
||||
from .factories import CourseEnrollmentAllowedFactory
|
||||
import datetime
|
||||
from django.utils.timezone import UTC
|
||||
|
||||
|
||||
class AccessTestCase(TestCase):
|
||||
@@ -77,7 +71,7 @@ class AccessTestCase(TestCase):
|
||||
# TODO: override DISABLE_START_DATES and test the start date branch of the method
|
||||
u = Mock()
|
||||
d = Mock()
|
||||
d.start = time.gmtime(time.time() - 86400) # make sure the start time is in the past
|
||||
d.start = datetime.datetime.now(UTC()) - datetime.timedelta(days=1) # make sure the start time is in the past
|
||||
|
||||
# Always returns true because DISABLE_START_DATES is set in test.py
|
||||
self.assertTrue(access._has_access_descriptor(u, d, 'load'))
|
||||
@@ -85,8 +79,8 @@ class AccessTestCase(TestCase):
|
||||
|
||||
def test__has_access_course_desc_can_enroll(self):
|
||||
u = Mock()
|
||||
yesterday = time.gmtime(time.time() - 86400)
|
||||
tomorrow = time.gmtime(time.time() + 86400)
|
||||
yesterday = datetime.datetime.now(UTC()) - datetime.timedelta(days=1)
|
||||
tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1)
|
||||
c = Mock(enrollment_start=yesterday, enrollment_end=tomorrow)
|
||||
|
||||
# User can enroll if it is between the start and end dates
|
||||
|
||||
@@ -3,7 +3,6 @@ Test for lms courseware app
|
||||
'''
|
||||
import logging
|
||||
import json
|
||||
import time
|
||||
import random
|
||||
|
||||
from urlparse import urlsplit, urlunsplit
|
||||
@@ -30,6 +29,8 @@ from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.xml_importer import import_from_xml
|
||||
from xmodule.modulestore.xml import XMLModuleStore
|
||||
import datetime
|
||||
from django.utils.timezone import UTC
|
||||
|
||||
log = logging.getLogger("mitx." + __name__)
|
||||
|
||||
@@ -603,9 +604,9 @@ class TestViewAuth(LoginEnrollmentTestCase):
|
||||
"""Actually do the test, relying on settings to be right."""
|
||||
|
||||
# Make courses start in the future
|
||||
tomorrow = time.time() + 24 * 3600
|
||||
self.toy.lms.start = time.gmtime(tomorrow)
|
||||
self.full.lms.start = time.gmtime(tomorrow)
|
||||
tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1)
|
||||
self.toy.lms.start = tomorrow
|
||||
self.full.lms.start = tomorrow
|
||||
|
||||
self.assertFalse(self.toy.has_started())
|
||||
self.assertFalse(self.full.has_started())
|
||||
@@ -728,18 +729,18 @@ class TestViewAuth(LoginEnrollmentTestCase):
|
||||
"""Actually do the test, relying on settings to be right."""
|
||||
|
||||
# Make courses start in the future
|
||||
tomorrow = time.time() + 24 * 3600
|
||||
nextday = tomorrow + 24 * 3600
|
||||
yesterday = time.time() - 24 * 3600
|
||||
tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1)
|
||||
nextday = tomorrow + datetime.timedelta(days=1)
|
||||
yesterday = datetime.datetime.now(UTC()) - datetime.timedelta(days=1)
|
||||
|
||||
print "changing"
|
||||
# toy course's enrollment period hasn't started
|
||||
self.toy.enrollment_start = time.gmtime(tomorrow)
|
||||
self.toy.enrollment_end = time.gmtime(nextday)
|
||||
self.toy.enrollment_start = tomorrow
|
||||
self.toy.enrollment_end = nextday
|
||||
|
||||
# full course's has
|
||||
self.full.enrollment_start = time.gmtime(yesterday)
|
||||
self.full.enrollment_end = time.gmtime(tomorrow)
|
||||
self.full.enrollment_start = yesterday
|
||||
self.full.enrollment_end = tomorrow
|
||||
|
||||
print "login"
|
||||
# First, try with an enrolled student
|
||||
@@ -778,12 +779,10 @@ class TestViewAuth(LoginEnrollmentTestCase):
|
||||
self.assertFalse(settings.MITX_FEATURES['DISABLE_START_DATES'])
|
||||
|
||||
# Make courses start in the future
|
||||
tomorrow = time.time() + 24 * 3600
|
||||
# nextday = tomorrow + 24 * 3600
|
||||
# yesterday = time.time() - 24 * 3600
|
||||
tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1)
|
||||
|
||||
# toy course's hasn't started
|
||||
self.toy.lms.start = time.gmtime(tomorrow)
|
||||
self.toy.lms.start = tomorrow
|
||||
self.assertFalse(self.toy.has_started())
|
||||
|
||||
# but should be accessible for beta testers
|
||||
@@ -854,7 +853,7 @@ class TestSubmittingProblems(LoginEnrollmentTestCase):
|
||||
modx_url = self.modx_url(problem_location, 'problem_check')
|
||||
answer_key_prefix = 'input_i4x-edX-{}-problem-{}_'.format(self.course_slug, problem_url_name)
|
||||
resp = self.client.post(modx_url,
|
||||
{ (answer_key_prefix + k): v for k,v in responses.items() }
|
||||
{ (answer_key_prefix + k): v for k, v in responses.items() }
|
||||
)
|
||||
return resp
|
||||
|
||||
@@ -925,7 +924,7 @@ class TestCourseGrader(TestSubmittingProblems):
|
||||
# Only get half of the first problem correct
|
||||
self.submit_question_answer('H1P1', {'2_1': 'Correct', '2_2': 'Incorrect'})
|
||||
self.check_grade_percent(0.06)
|
||||
self.assertEqual(earned_hw_scores(), [1.0, 0, 0]) # Order matters
|
||||
self.assertEqual(earned_hw_scores(), [1.0, 0, 0]) # Order matters
|
||||
self.assertEqual(score_for_hw('Homework1'), [1.0, 0.0])
|
||||
|
||||
# Get both parts of the first problem correct
|
||||
@@ -958,16 +957,16 @@ class TestCourseGrader(TestSubmittingProblems):
|
||||
|
||||
# Third homework
|
||||
self.submit_question_answer('H3P1', {'2_1': 'Correct', '2_2': 'Correct'})
|
||||
self.check_grade_percent(0.42) # Score didn't change
|
||||
self.check_grade_percent(0.42) # Score didn't change
|
||||
self.assertEqual(earned_hw_scores(), [4.0, 4.0, 2.0])
|
||||
|
||||
self.submit_question_answer('H3P2', {'2_1': 'Correct', '2_2': 'Correct'})
|
||||
self.check_grade_percent(0.5) # Now homework2 dropped. Score changes
|
||||
self.check_grade_percent(0.5) # Now homework2 dropped. Score changes
|
||||
self.assertEqual(earned_hw_scores(), [4.0, 4.0, 4.0])
|
||||
|
||||
# Now we answer the final question (worth half of the grade)
|
||||
self.submit_question_answer('FinalQuestion', {'2_1': 'Correct', '2_2': 'Correct'})
|
||||
self.check_grade_percent(1.0) # Hooray! We got 100%
|
||||
self.check_grade_percent(1.0) # Hooray! We got 100%
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
|
||||
@@ -1000,7 +999,7 @@ class TestSchematicResponse(TestSubmittingProblems):
|
||||
{ '2_1': json.dumps(
|
||||
[['transient', {'Z': [
|
||||
[0.0000004, 2.8],
|
||||
[0.0000009, 0.0], # wrong.
|
||||
[0.0000009, 0.0], # wrong.
|
||||
[0.0000014, 2.8],
|
||||
[0.0000019, 2.8],
|
||||
[0.0000024, 2.8],
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import time
|
||||
from collections import defaultdict
|
||||
import logging
|
||||
import time
|
||||
import urllib
|
||||
from datetime import datetime
|
||||
|
||||
from courseware.module_render import get_module
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.search import path_to_location
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import connection
|
||||
@@ -16,13 +11,12 @@ from django.http import HttpResponse
|
||||
from django.utils import simplejson
|
||||
from django_comment_common.models import Role
|
||||
from django_comment_client.permissions import check_permissions_by_view
|
||||
from xmodule.modulestore.exceptions import NoPathToItem
|
||||
|
||||
from mitxmako import middleware
|
||||
import pystache_custom as pystache
|
||||
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from django.utils.timezone import UTC
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -100,7 +94,7 @@ def get_discussion_category_map(course):
|
||||
|
||||
def filter_unstarted_categories(category_map):
|
||||
|
||||
now = time.gmtime()
|
||||
now = datetime.now(UTC())
|
||||
|
||||
result_map = {}
|
||||
|
||||
@@ -220,7 +214,7 @@ def initialize_discussion_info(course):
|
||||
for topic, entry in course.discussion_topics.items():
|
||||
category_map['entries'][topic] = {"id": entry["id"],
|
||||
"sort_key": entry.get("sort_key", topic),
|
||||
"start_date": time.gmtime()}
|
||||
"start_date": datetime.now(UTC())}
|
||||
sort_map_entries(category_map)
|
||||
|
||||
_DISCUSSIONINFO[course.id]['id_map'] = discussion_id_map
|
||||
@@ -292,7 +286,7 @@ def get_ability(course_id, content, user):
|
||||
'can_vote': check_permissions_by_view(user, course_id, content, "vote_for_thread" if content['type'] == 'thread' else "vote_for_comment"),
|
||||
}
|
||||
|
||||
#TODO: RENAME
|
||||
# TODO: RENAME
|
||||
|
||||
|
||||
def get_annotated_content_info(course_id, content, user, user_info):
|
||||
@@ -310,7 +304,7 @@ def get_annotated_content_info(course_id, content, user, user_info):
|
||||
'ability': get_ability(course_id, content, user),
|
||||
}
|
||||
|
||||
#TODO: RENAME
|
||||
# TODO: RENAME
|
||||
|
||||
|
||||
def get_annotated_content_infos(course_id, thread, user, user_info):
|
||||
|
||||
Reference in New Issue
Block a user