Merge branch 'master' into blapenta/fix-test-coverage

Conflicts:
	AUTHORS
	common/lib/xmodule/xmodule/tests/test_annotatable_module.py
	common/lib/xmodule/xmodule/tests/test_capa_module.py
	common/lib/xmodule/xmodule/tests/test_combined_open_ended.py
	common/lib/xmodule/xmodule/tests/test_conditional.py
	common/lib/xmodule/xmodule/tests/test_html_module.py
	common/lib/xmodule/xmodule/tests/test_progress.py
	common/lib/xmodule/xmodule/tests/test_xml_module.py
	lms/djangoapps/courseware/tests/__init__.py
This commit is contained in:
lapentab
2013-06-18 13:41:53 -04:00
206 changed files with 5544 additions and 1415 deletions

View File

@@ -1,18 +0,0 @@
Instructions
============
For each pull request, add one or more lines to the bottom of the change list. When
code is released to production, change the `Upcoming` entry to todays date, and add
a new block at the bottom of the file.
Upcoming
--------
Change log entries should be targeted at end users. A good place to start is the
user story that instigated the pull request.
Changes
=======
Upcoming
--------
* Created changelog

View File

@@ -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:

View File

@@ -0,0 +1,25 @@
Feature: Navigate Course
As a student in an edX course
In order to view the course properly
I want to be able to navigate through the content
Scenario: I can navigate to a section
Given I am viewing a course with multiple sections
When I click on section "2"
Then I should see the content of section "2"
Scenario: I can navigate to subsections
Given I am viewing a section with multiple subsections
When I click on subsection "2"
Then I should see the content of subsection "2"
Scenario: I can navigate to sequences
Given I am viewing a section with multiple sequences
When I click on sequence "2"
Then I should see the content of sequence "2"
Scenario: I can go back to where I was after I log out and back in
Given I am viewing a course with multiple sections
When I click on section "2"
And I return later
Then I should see that I was most recently in section "2"

View File

@@ -0,0 +1,186 @@
#pylint: disable=C0111
#pylint: disable=W0621
from lettuce import world, step
from django.contrib.auth.models import User
from lettuce.django import django_url
from student.models import CourseEnrollment
from common import course_id, course_location
from problems_setup import PROBLEM_DICT
TEST_COURSE_ORG = 'edx'
TEST_COURSE_NAME = 'Test Course'
TEST_SECTION_NAME = 'Test Section'
TEST_SUBSECTION_NAME = 'Test Subsection'
@step(u'I am viewing a course with multiple sections')
def view_course_multiple_sections(step):
create_course()
# Add a section to the course to contain problems
section1 = world.ItemFactory.create(parent_location=course_location('model_course'),
display_name=section_name(1))
# Add a section to the course to contain problems
section2 = world.ItemFactory.create(parent_location=course_location('model_course'),
display_name=section_name(2))
place1 = world.ItemFactory.create(parent_location=section1.location,
template='i4x://edx/templates/sequential/Empty',
display_name=subsection_name(1))
place2 = world.ItemFactory.create(parent_location=section2.location,
template='i4x://edx/templates/sequential/Empty',
display_name=subsection_name(2))
add_problem_to_course_section('model_course', 'multiple choice', place1.location)
add_problem_to_course_section('model_course', 'drop down', place2.location)
create_user_and_visit_course()
@step(u'I am viewing a section with multiple subsections')
def view_course_multiple_subsections(step):
create_course()
# Add a section to the course to contain problems
section1 = world.ItemFactory.create(parent_location=course_location('model_course'),
display_name=section_name(1))
place1 = world.ItemFactory.create(parent_location=section1.location,
template='i4x://edx/templates/sequential/Empty',
display_name=subsection_name(1))
place2 = world.ItemFactory.create(parent_location=section1.location,
display_name=subsection_name(2))
add_problem_to_course_section('model_course', 'multiple choice', place1.location)
add_problem_to_course_section('model_course', 'drop down', place2.location)
create_user_and_visit_course()
@step(u'I am viewing a section with multiple sequences')
def view_course_multiple_sequences(step):
create_course()
# Add a section to the course to contain problems
section1 = world.ItemFactory.create(parent_location=course_location('model_course'),
display_name=section_name(1))
place1 = world.ItemFactory.create(parent_location=section1.location,
template='i4x://edx/templates/sequential/Empty',
display_name=subsection_name(1))
add_problem_to_course_section('model_course', 'multiple choice', place1.location)
add_problem_to_course_section('model_course', 'drop down', place1.location)
create_user_and_visit_course()
@step(u'I click on section "([^"]*)"$')
def click_on_section(step, section):
section_css = 'h3[tabindex="-1"]'
world.css_click(section_css)
subid = "ui-accordion-accordion-panel-" + str(int(section) - 1)
subsection_css = 'ul[id="%s"]> li > a' % subid
#for some reason needed to do it in two steps
world.css_find(subsection_css).click()
@step(u'I click on subsection "([^"]*)"$')
def click_on_subsection(step, subsection):
subsection_css = 'ul[id="ui-accordion-accordion-panel-0"]> li > a'
world.css_find(subsection_css)[int(subsection) - 1].click()
@step(u'I click on sequence "([^"]*)"$')
def click_on_sequence(step, sequence):
sequence_css = 'a[data-element="%s"]' % sequence
world.css_click(sequence_css)
@step(u'I should see the content of (?:sub)?section "([^"]*)"$')
def see_section_content(step, section):
if section == "2":
text = 'The correct answer is Option 2'
elif section == "1":
text = 'The correct answer is Choice 3'
step.given('I should see "' + text + '" somewhere on the page')
@step(u'I should see the content of sequence "([^"]*)"$')
def see_sequence_content(step, sequence):
step.given('I should see the content of section "2"')
@step(u'I return later')
def return_to_course(step):
step.given('I visit the homepage')
world.click_link("View Course")
world.click_link("Courseware")
@step(u'I should see that I was most recently in section "([^"]*)"$')
def see_recent_section(step, section):
step.given('I should see "You were most recently in %s" somewhere on the page' % subsection_name(int(section)))
#####################
# HELPERS
#####################
def section_name(section):
return TEST_SECTION_NAME + str(section)
def subsection_name(section):
return TEST_SUBSECTION_NAME + str(section)
def create_course():
world.clear_courses()
world.CourseFactory.create(org=TEST_COURSE_ORG,
number="model_course",
display_name=TEST_COURSE_NAME)
def create_user_and_visit_course():
world.create_user('robot')
u = User.objects.get(username='robot')
CourseEnrollment.objects.get_or_create(user=u, course_id=course_id("model_course"))
world.log_in('robot', 'test')
chapter_name = (TEST_SECTION_NAME + "1").replace(" ", "_")
section_name = (TEST_SUBSECTION_NAME + "1").replace(" ", "_")
url = django_url('/courses/edx/model_course/Test_Course/courseware/%s/%s' %
(chapter_name, section_name))
world.browser.visit(url)
def add_problem_to_course_section(course, problem_type, parent_location, extraMeta=None):
'''
Add a problem to the course we have created using factories.
'''
assert(problem_type in PROBLEM_DICT)
# Generate the problem XML using capa.tests.response_xml_factory
factory_dict = PROBLEM_DICT[problem_type]
problem_xml = factory_dict['factory'].build_xml(**factory_dict['kwargs'])
metadata = {'rerandomize': 'always'} if not 'metadata' in factory_dict else factory_dict['metadata']
if extraMeta:
metadata = dict(metadata, **extraMeta)
# Create a problem item using our generated XML
# We set rerandomize=always in the metadata so that the "Reset" button
# will appear.
template_name = "i4x://edx/templates/problem/Blank_Common_Problem"
world.ItemFactory.create(parent_location=parent_location,
template=template_name,
display_name=str(problem_type),
data=problem_xml,
metadata=metadata)

View File

@@ -112,10 +112,10 @@ Feature: Answer problems
Scenario: I can view and hide the answer if the problem has it:
Given I am viewing a "numerical" that shows the answer "always"
When I press the "Show Answer" button
Then The "Hide Answer" button does appear
And The "Show Answer" button does not appear
When I press the button with the label "Show Answer(s)"
Then the button with the label "Hide Answer(s)" does appear
And the button with the label "Show Answer(s)" does not appear
And I should see "4.14159" somewhere in the page
When I press the "Hide Answer" button
Then The "Show Answer" button does appear
When I press the button with the label "Hide Answer(s)"
Then the button with the label "Show Answer(s)" does appear
And I should not see "4.14159" anywhere on the page

View File

@@ -9,7 +9,7 @@ from lettuce import world, step
from lettuce.django import django_url
from common import i_am_registered_for_the_course, TEST_SECTION_NAME
from problems_setup import PROBLEM_DICT, answer_problem, problem_has_answer, add_problem_to_course
from nose.tools import assert_equal, assert_not_equal
@step(u'I am viewing a "([^"]*)" problem with "([^"]*)" attempt')
def view_problem_with_attempts(step, problem_type, attempts):
@@ -116,6 +116,14 @@ def reset_problem(step):
world.css_click('input.reset')
@step(u'I press the button with the label "([^"]*)"$')
def press_the_button_with_label(step, buttonname):
button_css = 'button span.show-label'
elem = world.css_find(button_css).first
assert_equal(elem.text, buttonname)
elem.click()
@step(u'The "([^"]*)" button does( not)? appear')
def action_button_present(step, buttonname, doesnt_appear):
button_css = 'section.action input[value*="%s"]' % buttonname
@@ -125,6 +133,16 @@ def action_button_present(step, buttonname, doesnt_appear):
assert world.is_css_present(button_css)
@step(u'the button with the label "([^"]*)" does( not)? appear')
def button_with_label_present(step, buttonname, doesnt_appear):
button_css = 'button span.show-label'
elem = world.css_find(button_css).first
if doesnt_appear:
assert_not_equal(elem.text, buttonname)
else:
assert_equal(elem.text, buttonname)
@step(u'My "([^"]*)" answer is marked "([^"]*)"')
def assert_answer_mark(step, problem_type, correctness):
"""

View File

@@ -0,0 +1,6 @@
Feature: Video Alpha component
As a student, I want to view course videos in LMS.
Scenario: Autoplay is enabled in LMS
Given the course has a Video component
Then when I view the video it has autoplay enabled

View File

@@ -0,0 +1,36 @@
#pylint: disable=C0111
#pylint: disable=W0613
#pylint: disable=W0621
from lettuce import world, step
from lettuce.django import django_url
from common import TEST_COURSE_NAME, TEST_SECTION_NAME, i_am_registered_for_the_course, section_location
############### ACTIONS ####################
@step('when I view the video it has autoplay enabled')
def does_autoplay(step):
assert(world.css_find('.videoalpha')[0]['data-autoplay'] == 'True')
@step('the course has a Video component')
def view_videoalpha(step):
coursename = TEST_COURSE_NAME.replace(' ', '_')
i_am_registered_for_the_course(step, coursename)
# Make sure we have a videoalpha
add_videoalpha_to_course(coursename)
chapter_name = TEST_SECTION_NAME.replace(" ", "_")
section_name = chapter_name
url = django_url('/courses/edx/Test_Course/Test_Course/courseware/%s/%s' %
(chapter_name, section_name))
world.browser.visit(url)
def add_videoalpha_to_course(course):
template_name = 'i4x://edx/templates/videoalpha/default'
world.ItemFactory.create(parent_location=section_location(course),
template=template_name,
display_name='Video Alpha 1')

View File

@@ -364,7 +364,7 @@ def get_score(course_id, user, problem_descriptor, module_creator, model_data_ca
else:
return (None, None)
if not (problem_descriptor.stores_state and problem_descriptor.has_score):
if not problem_descriptor.has_score:
# These are not problems, and do not have a score
return (None, None)

View File

@@ -25,8 +25,8 @@ class BaseTestXmodule(ModuleStoreTestCase):
"""Base class for testing Xmodules with mongo store.
This class prepares course and users for tests:
1. create test course
2. create, enrol and login users for this course
1. create test course;
2. create, enrol and login users for this course;
Any xmodule should overwrite only next parameters for test:
1. TEMPLATE_NAME
@@ -77,14 +77,15 @@ class BaseTestXmodule(ModuleStoreTestCase):
data=self.DATA
)
location = self.item_descriptor.location
system = get_test_system()
system.render_template = lambda template, context: context
model_data = {'location': self.item_descriptor.location}
model_data.update(self.MODEL_DATA)
self.item_module = self.item_descriptor.module_class(
system, location, self.item_descriptor, self.MODEL_DATA
system, self.item_descriptor, model_data
)
self.item_url = Location(location).url()
self.item_url = Location(self.item_module.location).url()
# login all users for acces to Xmodule
self.clients = {user.username: Client() for user in self.users}

View File

@@ -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):

View File

@@ -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

View File

@@ -64,7 +64,7 @@ class TestStaffMasqueradeAsStudent(LoginEnrollmentTestCase):
def test_staff_debug_for_staff(self):
resp = self.get_cw_section()
sdebug = '<div><a href="#i4x_edX_graded_problem_H1P1_debug" id="i4x_edX_graded_problem_H1P1_trig">Staff Debug Info</a></div>'
self.assertTrue(sdebug in resp.content)
@@ -84,9 +84,9 @@ class TestStaffMasqueradeAsStudent(LoginEnrollmentTestCase):
resp = self.get_cw_section()
sdebug = '<div><a href="#i4x_edX_graded_problem_H1P1_debug" id="i4x_edX_graded_problem_H1P1_trig">Staff Debug Info</a></div>'
self.assertFalse(sdebug in resp.content)
def get_problem(self):
pun = 'H1P1'
problem_location = "i4x://edX/graded/problem/%s" % pun
@@ -105,7 +105,7 @@ class TestStaffMasqueradeAsStudent(LoginEnrollmentTestCase):
resp = self.get_problem()
html = json.loads(resp.content)['html']
print html
sabut = '<input class="show" type="button" value="Show Answer">'
sabut = '<button class="show"><span class="show-label">Show Answer(s)</span> <span class="sr">(for question(s) above - adjacent to each field)</span></button>'
self.assertTrue(sabut in html)
def test_no_showanswer_for_student(self):
@@ -116,5 +116,5 @@ class TestStaffMasqueradeAsStudent(LoginEnrollmentTestCase):
resp = self.get_problem()
html = json.loads(resp.content)['html']
print html
sabut = '<input class="show" type="button" value="Show Answer">'
sabut = '<button class="show"><span class="show-label">Show Answer(s)</span> <span class="sr">(for question(s) above - adjacent to each field)</span></button>'
self.assertFalse(sabut in html)

View File

@@ -26,7 +26,6 @@ def mock_field(scope, name):
def mock_descriptor(fields=[], lms_fields=[]):
descriptor = Mock()
descriptor.stores_state = True
descriptor.location = location('def_id')
descriptor.module_class.fields = fields
descriptor.module_class.lms.fields = lms_fields

View File

@@ -15,8 +15,8 @@ class TestVideo(BaseTestXmodule):
user.username: self.clients[user.username].post(
self.get_url('whatever'),
{},
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
for user in self.users
HTTP_X_REQUESTED_WITH='XMLHttpRequest'
) for user in self.users
}
self.assertEqual(

View File

@@ -1,135 +0,0 @@
# -*- coding: utf-8 -*-
"""Test for Video Xmodule functional logic.
These tests data readed from xml, not from mongo.
We have a ModuleStoreTestCase class defined in
common/lib/xmodule/xmodule/modulestore/tests/django_utils.py.
You can search for usages of this in the cms and lms tests for examples.
You use this so that it will do things like point the modulestore
setting to mongo, flush the contentstore before and after, load the
templates, etc.
You can then use the CourseFactory and XModuleItemFactory as defined in
common/lib/xmodule/xmodule/modulestore/tests/factories.py to create the
course, section, subsection, unit, etc.
"""
import json
import unittest
from mock import Mock
from lxml import etree
from xmodule.video_module import VideoDescriptor, VideoModule
from xmodule.modulestore import Location
from xmodule.tests import get_test_system
from xmodule.tests.test_logic import LogicTest
class VideoFactory(object):
"""A helper class to create video modules with various parameters
for testing.
"""
# tag that uses youtube videos
sample_problem_xml_youtube = """
<video show_captions="true"
youtube="0.75:jNCf2gIqpeE,1.0:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg"
data_dir=""
caption_asset_path=""
autoplay="true"
from="01:00:03" to="01:00:10"
>
<source src=".../mit-3091x/M-3091X-FA12-L21-3_100.mp4"/>
</video>
"""
@staticmethod
def create():
"""Method return Video Xmodule instance."""
location = Location(["i4x", "edX", "video", "default",
"SampleProblem1"])
model_data = {'data': VideoFactory.sample_problem_xml_youtube}
descriptor = Mock(weight="1")
system = get_test_system()
system.render_template = lambda template, context: context
module = VideoModule(system, location, descriptor, model_data)
return module
class VideoModuleLogicTest(LogicTest):
"""Tests for logic of Video Xmodule."""
descriptor_class = VideoDescriptor
raw_model_data = {
'data': '<video />'
}
def test_get_timeframe_no_parameters(self):
"""Make sure that timeframe() works correctly w/o parameters"""
xmltree = etree.fromstring('<video>test</video>')
output = self.xmodule.get_timeframe(xmltree)
self.assertEqual(output, ('', ''))
def test_get_timeframe_with_one_parameter(self):
"""Make sure that timeframe() works correctly with one parameter"""
xmltree = etree.fromstring(
'<video from="00:04:07">test</video>'
)
output = self.xmodule.get_timeframe(xmltree)
self.assertEqual(output, (247, ''))
def test_get_timeframe_with_two_parameters(self):
"""Make sure that timeframe() works correctly with two parameters"""
xmltree = etree.fromstring(
'''<video
from="00:04:07"
to="13:04:39"
>test</video>'''
)
output = self.xmodule.get_timeframe(xmltree)
self.assertEqual(output, (247, 47079))
class VideoModuleUnitTest(unittest.TestCase):
"""Unit tests for Video Xmodule."""
def test_video_constructor(self):
"""Make sure that all parameters extracted correclty from xml"""
module = VideoFactory.create()
# `get_html` return only context, cause we
# overwrite `system.render_template`
context = module.get_html()
expected_context = {
'track': None,
'show_captions': 'true',
'display_name': 'SampleProblem1',
'id': module.location.html_id(),
'end': 3610.0,
'caption_asset_path': '/static/subs/',
'source': '.../mit-3091x/M-3091X-FA12-L21-3_100.mp4',
'streams': '0.75:jNCf2gIqpeE,1.0:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg',
'normal_speed_video_id': 'ZwkTiUPN0mg',
'position': 0,
'start': 3603.0
}
self.assertDictEqual(context, expected_context)
self.assertEqual(
module.youtube,
'0.75:jNCf2gIqpeE,1.0:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg')
self.assertEqual(
module.video_list(),
module.youtube)
self.assertEqual(
module.position,
0)
self.assertDictEqual(
json.loads(module.get_instance_state()),
{'position': 0})

View File

@@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
"""Video xmodule tests in mongo."""
from . import BaseTestXmodule
from .test_videoalpha_xml import SOURCE_XML
from django.conf import settings
class TestVideo(BaseTestXmodule):
"""Integration tests: web client + mongo."""
TEMPLATE_NAME = "i4x://edx/templates/videoalpha/Video_Alpha"
DATA = SOURCE_XML
MODEL_DATA = {
'data': DATA
}
def test_handle_ajax_dispatch(self):
responses = {
user.username: self.clients[user.username].post(
self.get_url('whatever'),
{},
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
for user in self.users
}
self.assertEqual(
set([
response.status_code
for _, response in responses.items()
]).pop(),
404)
def test_videoalpha_constructor(self):
"""Make sure that all parameters extracted correclty from xml"""
# `get_html` return only context, cause we
# overwrite `system.render_template`
context = self.item_module.get_html()
expected_context = {
'data_dir': getattr(self, 'data_dir', None),
'caption_asset_path': '/c4x/MITx/999/asset/subs_',
'show_captions': self.item_module.show_captions,
'display_name': self.item_module.display_name_with_default,
'end': self.item_module.end_time,
'id': self.item_module.location.html_id(),
'sources': self.item_module.sources,
'start': self.item_module.start_time,
'sub': self.item_module.sub,
'track': self.item_module.track,
'youtube_streams': self.item_module.youtube_streams,
'autoplay': settings.MITX_FEATURES.get('AUTOPLAY_VIDEOS', True)
}
self.assertDictEqual(context, expected_context)

View File

@@ -0,0 +1,129 @@
# -*- coding: utf-8 -*-
"""Test for VideoAlpha Xmodule functional logic.
These tests data readed from xml, not from mongo.
We have a ModuleStoreTestCase class defined in
common/lib/xmodule/xmodule/modulestore/tests/django_utils.py.
You can search for usages of this in the cms and lms tests for examples.
You use this so that it will do things like point the modulestore
setting to mongo, flush the contentstore before and after, load the
templates, etc.
You can then use the CourseFactory and XModuleItemFactory as defined in
common/lib/xmodule/xmodule/modulestore/tests/factories.py to create the
course, section, subsection, unit, etc.
"""
import json
import unittest
from mock import Mock
from lxml import etree
from django.conf import settings
from xmodule.videoalpha_module import VideoAlphaDescriptor, VideoAlphaModule
from xmodule.modulestore import Location
from xmodule.tests import test_system
from xmodule.tests.test_logic import LogicTest
SOURCE_XML = """
<videoalpha show_captions="true"
youtube="0.75:jNCf2gIqpeE,1.0:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg"
data_dir=""
caption_asset_path=""
autoplay="true"
start_time="01:00:03" end_time="01:00:10"
>
<source src=".../mit-3091x/M-3091X-FA12-L21-3_100.mp4"/>
<source src=".../mit-3091x/M-3091X-FA12-L21-3_100.webm"/>
<source src=".../mit-3091x/M-3091X-FA12-L21-3_100.ogv"/>
</videoalpha>
"""
class VideoAlphaFactory(object):
"""A helper class to create videoalpha modules with various parameters
for testing.
"""
# tag that uses youtube videos
sample_problem_xml_youtube = SOURCE_XML
@staticmethod
def create():
"""Method return VideoAlpha Xmodule instance."""
location = Location(["i4x", "edX", "videoalpha", "default",
"SampleProblem1"])
model_data = {'data': VideoAlphaFactory.sample_problem_xml_youtube}
descriptor = Mock(weight="1")
system = test_system()
system.render_template = lambda template, context: context
VideoAlphaModule.location = location
module = VideoAlphaModule(system, descriptor, model_data)
return module
class VideoAlphaModuleTest(LogicTest):
"""Tests for logic of VideoAlpha Xmodule."""
descriptor_class = VideoAlphaDescriptor
raw_model_data = {
'data': '<videoalpha />'
}
def test_get_timeframe_no_parameters(self):
xmltree = etree.fromstring('<videoalpha>test</videoalpha>')
output = self.xmodule.get_timeframe(xmltree)
self.assertEqual(output, ('', ''))
def test_get_timeframe_with_one_parameter(self):
xmltree = etree.fromstring(
'<videoalpha start_time="00:04:07">test</videoalpha>'
)
output = self.xmodule.get_timeframe(xmltree)
self.assertEqual(output, (247, ''))
def test_get_timeframe_with_two_parameters(self):
xmltree = etree.fromstring(
'''<videoalpha
start_time="00:04:07"
end_time="13:04:39"
>test</videoalpha>'''
)
output = self.xmodule.get_timeframe(xmltree)
self.assertEqual(output, (247, 47079))
class VideoAlphaModuleUnitTest(unittest.TestCase):
"""Unit tests for VideoAlpha Xmodule."""
def test_videoalpha_constructor(self):
"""Make sure that all parameters extracted correclty from xml"""
module = VideoAlphaFactory.create()
# `get_html` return only context, cause we
# overwrite `system.render_template`
context = module.get_html()
expected_context = {
'caption_asset_path': '/static/subs/',
'sub': module.sub,
'data_dir': getattr(self, 'data_dir', None),
'display_name': module.display_name_with_default,
'end': module.end_time,
'start': module.start_time,
'id': module.location.html_id(),
'show_captions': module.show_captions,
'sources': module.sources,
'youtube_streams': module.youtube_streams,
'track': module.track,
'autoplay': settings.MITX_FEATURES.get('AUTOPLAY_VIDEOS', True)
}
self.assertDictEqual(context, expected_context)
self.assertDictEqual(
json.loads(module.get_instance_state()),
{'position': 0})

View File

@@ -13,6 +13,7 @@ from xmodule.modulestore.django import modulestore
import courseware.views as views
from xmodule.modulestore import Location
from pytz import UTC
class Stub():
@@ -63,7 +64,7 @@ class ViewsTestCase(TestCase):
def setUp(self):
self.user = User.objects.create(username='dummy', password='123456',
email='test@mit.edu')
self.date = datetime.datetime(2013, 1, 22)
self.date = datetime.datetime(2013, 1, 22, tzinfo=UTC)
self.course_id = 'edX/toy/2012_Fall'
self.enrollment = CourseEnrollment.objects.get_or_create(user=self.user,
course_id=self.course_id,

View File

@@ -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'
}
}
}
@@ -287,7 +288,7 @@ class PageLoaderTestCase(LoginEnrollmentTestCase):
'''
Choose a page in the course randomly, and assert that it loads
'''
# enroll in the course before trying to access pages
# enroll in the course before trying to access pages
courses = module_store.get_courses()
self.assertEqual(len(courses), 1)
course = courses[0]
@@ -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],

View File

@@ -1,5 +1,6 @@
import logging
from django.conf import settings
from django.test.utils import override_settings
from django.test.client import Client
from django.contrib.auth.models import User
@@ -8,6 +9,7 @@ from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from django.core.urlresolvers import reverse
from django.core.management import call_command
from util.testing import UrlResetMixin
from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE
from nose.tools import assert_true, assert_equal
@@ -18,8 +20,19 @@ log = logging.getLogger(__name__)
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
@patch('comment_client.utils.requests.request')
class ViewsTestCase(ModuleStoreTestCase):
class ViewsTestCase(UrlResetMixin, ModuleStoreTestCase):
def setUp(self):
# This feature affects the contents of urls.py, so we change
# it before the call to super.setUp() which reloads urls.py (because
# of the UrlResetMixin)
# This setting is cleaned up at the end of the test by @override_settings, which
# restores all of the old settings
settings.MITX_FEATURES['ENABLE_DISCUSSION_SERVICE'] = True
super(ViewsTestCase, self).setUp()
# create a course
self.course = CourseFactory.create(org='MITx', course='999',
display_name='Robot Super Course')

View File

@@ -1,14 +1,9 @@
import time
import pytz
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 = {}
@@ -175,7 +169,9 @@ def initialize_discussion_info(course):
category = " / ".join([x.strip() for x in category.split("/")])
last_category = category.split("/")[-1]
discussion_id_map[id] = {"location": module.location, "title": last_category + " / " + title}
unexpanded_category_map[category].append({"title": title, "id": id, "sort_key": sort_key, "start_date": module.lms.start})
#Handle case where module.lms.start is None
entry_start_date = module.lms.start if module.lms.start else datetime.max.replace(tzinfo=pytz.UTC)
unexpanded_category_map[category].append({"title": title, "id": id, "sort_key": sort_key, "start_date": entry_start_date})
category_map = {"entries": defaultdict(dict), "subcategories": defaultdict(dict)}
for category_path, entries in unexpanded_category_map.items():
@@ -220,12 +216,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 +288,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 +306,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):

View File

@@ -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)
@@ -222,7 +223,7 @@ class FolditTestCase(TestCase):
verify = {"Verify": verify_code(self.user.email, puzzles_str),
"VerifyMethod":"FoldItVerify"}
data = {'SetPuzzlesCompleteVerify': json.dumps(verify),
data = {'SetPuzzlesCompleteVerify': json.dumps(verify),
'SetPuzzlesComplete': puzzles_str}
request = self.make_request(data)

View File

@@ -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

View File

@@ -160,7 +160,7 @@ class TestPeerGradingService(LoginEnrollmentTestCase):
self.course_id = "edX/toy/2012_Fall"
self.toy = modulestore().get_course(self.course_id)
location = "i4x://edX/toy/peergrading/init"
model_data = {'data': "<peergrading/>"}
model_data = {'data': "<peergrading/>", 'location': location}
self.mock_service = peer_grading_service.MockPeerGradingService()
self.system = ModuleSystem(
ajax_url=location,
@@ -172,9 +172,9 @@ class TestPeerGradingService(LoginEnrollmentTestCase):
s3_interface=test_util_open_ended.S3_INTERFACE,
open_ended_grading_interface=test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE
)
self.descriptor = peer_grading_module.PeerGradingDescriptor(self.system, location, model_data)
model_data = {}
self.peer_module = peer_grading_module.PeerGradingModule(self.system, location, self.descriptor, model_data)
self.descriptor = peer_grading_module.PeerGradingDescriptor(self.system, model_data)
model_data = {'location': location}
self.peer_module = peer_grading_module.PeerGradingModule(self.system, self.descriptor, model_data)
self.peer_module.peer_gs = self.mock_service
self.logout()

View File

@@ -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")
@@ -110,7 +111,7 @@ def make_histogram(ydata, bins=None):
nbins = len(bins)
hist = dict(zip(bins, [0] * nbins))
for y in ydata:
for b in bins[::-1]: # in reverse order
for b in bins[::-1]: # in reverse order
if y > b:
hist[b] += 1
break
@@ -149,7 +150,7 @@ def generate_plots_for_problem(problem):
agdat = pmdset.aggregate(Sum('attempts'), Max('attempts'))
max_attempts = agdat['attempts__max']
total_attempts = agdat['attempts__sum'] # not used yet
total_attempts = agdat['attempts__sum'] # not used yet
msg += "max attempts = %d" % max_attempts
@@ -200,7 +201,7 @@ def generate_plots_for_problem(problem):
dtsv = StatVar()
for pmd in pmdset:
try:
checktimes = eval(pmd.checktimes) # update log of attempt timestamps
checktimes = eval(pmd.checktimes) # update log of attempt timestamps
except:
continue
if len(checktimes) < 2:
@@ -208,7 +209,7 @@ def generate_plots_for_problem(problem):
ct0 = checktimes[0]
for ct in checktimes[1:]:
dt = (ct - ct0).total_seconds() / 60.0
if dt < 20: # ignore if dt too long
if dt < 20: # ignore if dt too long
dtset.append(dt)
dtsv += dt
ct0 = ct
@@ -244,7 +245,7 @@ def generate_plots_for_problem(problem):
ylast = y + ylast
yset['ydat'] = ydat
if len(ydat) > 3: # try to fit to logistic function if enough data points
if len(ydat) > 3: # try to fit to logistic function if enough data points
try:
cfp = curve_fit(func_2pl, xdat, ydat, [1.0, max_attempts / 2.0])
yset['fitparam'] = cfp
@@ -337,10 +338,10 @@ def make_psychometrics_data_update_handler(course_id, user, module_state_key):
log.exception("no attempts for %s (state=%s)" % (sm, sm.state))
try:
checktimes = eval(pmd.checktimes) # update log of attempt timestamps
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()

View File

@@ -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

View File

@@ -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 = {

View File

@@ -172,17 +172,19 @@ for name, value in ENV_TOKENS.get("CODE_JAIL", {}).items():
COURSES_WITH_UNSAFE_CODE = ENV_TOKENS.get("COURSES_WITH_UNSAFE_CODE", [])
# If segment.io key specified, load it and turn on segment IO if the feature flag is set
SEGMENT_IO_LMS_KEY = ENV_TOKENS.get('SEGMENT_IO_LMS_KEY')
if SEGMENT_IO_LMS_KEY:
MITX_FEATURES['SEGMENT_IO_LMS'] = ENV_TOKENS.get('SEGMENT_IO_LMS', False)
############################## SECURE AUTH ITEMS ###############
# Secret things: passwords, access keys, etc.
with open(ENV_ROOT / CONFIG_PREFIX + "auth.json") as auth_file:
AUTH_TOKENS = json.load(auth_file)
############### Mixed Related(Secure/Not-Secure) Items ##########
# If Segment.io key specified, load it and enable Segment.io if the feature flag is set
SEGMENT_IO_LMS_KEY = AUTH_TOKENS.get('SEGMENT_IO_LMS_KEY')
if SEGMENT_IO_LMS_KEY:
MITX_FEATURES['SEGMENT_IO_LMS'] = ENV_TOKENS.get('SEGMENT_IO_LMS', False)
SECRET_KEY = AUTH_TOKENS['SECRET_KEY']
AWS_ACCESS_KEY_ID = AUTH_TOKENS["AWS_ACCESS_KEY_ID"]

24
lms/envs/aws_migrate.py Normal file
View File

@@ -0,0 +1,24 @@
"""
A Django settings file for use on AWS while running
database migrations, since we don't want to normally run the
LMS with enough privileges to modify the database schema.
"""
# We intentionally define lots of variables that aren't used, and
# want to import all variables from base settings files
# pylint: disable=W0401, W0614
# Import everything from .aws so that our settings are based on those.
from .aws import *
import os
from django.core.exceptions import ImproperlyConfigured
USER = os.environ.get('DB_MIGRATION_USER', 'root')
PASSWORD = os.environ.get('DB_MIGRATION_PASS', None)
if not PASSWORD:
raise ImproperlyConfigured("No database password was provided for running "
"migrations. This is fatal.")
DATABASES['default']['USER'] = USER
DATABASES['default']['PASSWORD'] = PASSWORD

View File

@@ -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 = {

View File

@@ -50,8 +50,8 @@ MITX_FEATURES = {
'SAMPLE': False,
'USE_DJANGO_PIPELINE': True,
'DISPLAY_HISTOGRAMS_TO_STAFF': True,
'REROUTE_ACTIVATION_EMAIL': False, # nonempty string = address for all activation emails
'DEBUG_LEVEL': 0, # 0 = lowest level, least verbose, 255 = max level, most verbose
'REROUTE_ACTIVATION_EMAIL': False, # nonempty string = address for all activation emails
'DEBUG_LEVEL': 0, # 0 = lowest level, least verbose, 255 = max level, most verbose
## DO NOT SET TO True IN THIS FILE
## Doing so will cause all courses to be released on production
@@ -67,13 +67,13 @@ MITX_FEATURES = {
# university to use for branding purposes
'SUBDOMAIN_BRANDING': False,
'FORCE_UNIVERSITY_DOMAIN': False, # set this to the university domain to use, as an override to HTTP_HOST
'FORCE_UNIVERSITY_DOMAIN': False, # set this to the university domain to use, as an override to HTTP_HOST
# set to None to do no university selection
'ENABLE_TEXTBOOK': True,
'ENABLE_DISCUSSION_SERVICE': True,
'ENABLE_PSYCHOMETRICS': False, # real-time psychometrics (eg item response theory analysis in instructor dashboard)
'ENABLE_PSYCHOMETRICS': False, # real-time psychometrics (eg item response theory analysis in instructor dashboard)
'ENABLE_DJANGO_ADMIN_SITE': False, # set true to enable django's admin site, even on prod (e.g. for course ops)
'ENABLE_SQL_TRACKING_LOGS': False,
@@ -84,7 +84,7 @@ MITX_FEATURES = {
'DISABLE_LOGIN_BUTTON': False, # used in systems where login is automatic, eg MIT SSL
'STUB_VIDEO_FOR_TESTING': False, # do not display video when running automated acceptance tests
'STUB_VIDEO_FOR_TESTING': False, # do not display video when running automated acceptance tests
# extrernal access methods
'ACCESS_REQUIRE_STAFF_FOR_COURSE': False,
@@ -132,7 +132,7 @@ DEFAULT_GROUPS = []
GENERATE_PROFILE_SCORES = False
# Used with XQueue
XQUEUE_WAITTIME_BETWEEN_REQUESTS = 5 # seconds
XQUEUE_WAITTIME_BETWEEN_REQUESTS = 5 # seconds
############################# SET PATH INFORMATION #############################
@@ -192,8 +192,8 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.static',
'django.contrib.messages.context_processors.messages',
#'django.core.context_processors.i18n',
'django.contrib.auth.context_processors.auth', # this is required for admin
'django.core.context_processors.csrf', # necessary for csrf protection
'django.contrib.auth.context_processors.auth', # this is required for admin
'django.core.context_processors.csrf', # necessary for csrf protection
# Added for django-wiki
'django.core.context_processors.media',
@@ -206,7 +206,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'mitxmako.shortcuts.marketing_link_context_processor',
)
STUDENT_FILEUPLOAD_MAX_SIZE = 4 * 1000 * 1000 # 4 MB
STUDENT_FILEUPLOAD_MAX_SIZE = 4 * 1000 * 1000 # 4 MB
MAX_FILEUPLOADS_PER_INPUT = 20
# FIXME:
@@ -216,7 +216,7 @@ LIB_URL = '/static/js/'
# Dev machines shouldn't need the book
# BOOK_URL = '/static/book/'
BOOK_URL = 'https://mitxstatic.s3.amazonaws.com/book_images/' # For AWS deploys
BOOK_URL = 'https://mitxstatic.s3.amazonaws.com/book_images/' # For AWS deploys
# RSS_URL = r'lms/templates/feed.rss'
# PRESS_URL = r''
RSS_TIMEOUT = 600
@@ -240,14 +240,14 @@ COURSE_TITLE = "Circuits and Electronics"
### Dark code. Should be enabled in local settings for devel.
ENABLE_MULTICOURSE = False # set to False to disable multicourse display (see lib.util.views.mitxhome)
ENABLE_MULTICOURSE = False # set to False to disable multicourse display (see lib.util.views.mitxhome)
WIKI_ENABLED = False
###
COURSE_DEFAULT = '6.002x_Fall_2012'
COURSE_SETTINGS = {'6.002x_Fall_2012': {'number': '6.002x',
COURSE_SETTINGS = {'6.002x_Fall_2012': {'number': '6.002x',
'title': 'Circuits and Electronics',
'xmlpath': '6002x/',
'location': 'i4x://edx/6002xs12/course/6.002x_Fall_2012',
@@ -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
@@ -342,8 +343,8 @@ STATICFILES_DIRS = [
FAVICON_PATH = 'images/favicon.ico'
# Locale/Internationalization
TIME_ZONE = 'America/New_York' # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
LANGUAGE_CODE = 'en' # http://www.i18nguy.com/unicode/language-identifiers.html
TIME_ZONE = 'America/New_York' # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
LANGUAGE_CODE = 'en' # http://www.i18nguy.com/unicode/language-identifiers.html
USE_I18N = True
USE_L10N = True
@@ -364,7 +365,7 @@ ALLOWED_GITRELOAD_IPS = ['207.97.227.253', '50.57.128.197', '108.171.174.178']
# setting is, I'm just bumping the expiration time to something absurd (100
# years). This is only used if DEFAULT_FILE_STORAGE is overriden to use S3
# in the global settings.py
AWS_QUERYSTRING_EXPIRE = 10 * 365 * 24 * 60 * 60 # 10 years
AWS_QUERYSTRING_EXPIRE = 10 * 365 * 24 * 60 * 60 # 10 years
################################# SIMPLEWIKI ###################################
SIMPLE_WIKI_REQUIRE_LOGIN_EDIT = True
@@ -373,8 +374,8 @@ SIMPLE_WIKI_REQUIRE_LOGIN_VIEW = False
################################# WIKI ###################################
WIKI_ACCOUNT_HANDLING = False
WIKI_EDITOR = 'course_wiki.editors.CodeMirror'
WIKI_SHOW_MAX_CHILDREN = 0 # We don't use the little menu that shows children of an article in the breadcrumb
WIKI_ANONYMOUS = False # Don't allow anonymous access until the styling is figured out
WIKI_SHOW_MAX_CHILDREN = 0 # We don't use the little menu that shows children of an article in the breadcrumb
WIKI_ANONYMOUS = False # Don't allow anonymous access until the styling is figured out
WIKI_CAN_CHANGE_PERMISSIONS = lambda article, user: user.is_staff or user.is_superuser
WIKI_CAN_ASSIGN = lambda article, user: user.is_staff or user.is_superuser
@@ -592,7 +593,7 @@ if os.path.isdir(DATA_DIR):
new_filename = os.path.splitext(filename)[0] + ".js"
if os.path.exists(js_dir / new_filename):
coffee_timestamp = os.stat(js_dir / filename).st_mtime
js_timestamp = os.stat(js_dir / new_filename).st_mtime
js_timestamp = os.stat(js_dir / new_filename).st_mtime
if coffee_timestamp <= js_timestamp:
continue
os.system("rm %s" % (js_dir / new_filename))
@@ -696,9 +697,9 @@ INSTALLED_APPS = (
'course_groups',
#For the wiki
'wiki', # The new django-wiki from benjaoming
'wiki', # The new django-wiki from benjaoming
'django_notify',
'course_wiki', # Our customizations
'course_wiki', # Our customizations
'mptt',
'sekizai',
#'wiki.plugins.attachments',
@@ -710,7 +711,7 @@ INSTALLED_APPS = (
'foldit',
# For testing
'django.contrib.admin', # only used in DEBUG mode
'django.contrib.admin', # only used in DEBUG mode
'debug',
# Discussion forums

View File

@@ -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'
}
}
}

View File

@@ -20,8 +20,10 @@ from path import path
# can test everything else :)
MITX_FEATURES['DISABLE_START_DATES'] = True
# Until we have discussion actually working in test mode, just turn it off
MITX_FEATURES['ENABLE_DISCUSSION_SERVICE'] = True
# Most tests don't use the discussion service, so we turn it off to speed them up.
# Tests that do can enable this flag, but must use the UrlResetMixin class to force urls.py
# to reload
MITX_FEATURES['ENABLE_DISCUSSION_SERVICE'] = False
MITX_FEATURES['ENABLE_SERVICE_STATUS'] = True

View File

@@ -209,7 +209,7 @@ mark {
}
.sr {
@include text-sr();
@extend .text-sr;
}
.help-tab {

View File

@@ -14,18 +14,6 @@
overflow: hidden;
}
// hidden elems - screenreaders
@mixin text-sr() {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
@mixin vertically-and-horizontally-centered ( $height, $width ) {
left: 50%;
margin-left: -$width / 2;
@@ -49,4 +37,18 @@
//-----------------
@mixin login_register_h1_style {}
@mixin footer_references_style {}
@mixin footer_references_style {}
// ====================
// extends -hidden elems - screenreaders
.text-sr {
border: 0;
clip: rect(1px 1px 1px 1px);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}

View File

@@ -3,14 +3,17 @@
<%def name="make_chapter(chapter)">
<div class="chapter">
<h3 ${' class="active"' if 'active' in chapter and chapter['active'] else ''}><a href="#">${chapter['display_name']}</a>
</h3>
<h3 ${' class="active"' if 'active' in chapter and chapter['active'] else ''} aria-label="${chapter['display_name']}${', current chapter' if 'active' in chapter and chapter['active'] else ''}">
<a href="#">
${chapter['display_name']}
</a>
</h3>
<ul>
% for section in chapter['sections']:
<li class="${'active' if 'active' in section and section['active'] else ''} ${'graded' if 'graded' in section and section['graded'] else ''}">
<a href="${reverse('courseware_section', args=[course_id, chapter['url_name'], section['url_name']])}">
<p>${section['display_name']}</p>
<p>${section['display_name']} ${'<span class="sr">, current section</span>' if 'active' in section and section['active'] else ''}</p>
<p class="subtitle">${section['format']} ${"due " + get_default_time_display(section['due'], show_timezone) if section.get('due') is not None else ''}</p>
</a>
</li>

View File

@@ -20,6 +20,9 @@ def url_class(is_active):
<li>
<a href="${tab.link | h}" class="${url_class(tab.is_active)}">
${tab.name | h}
% if tab.is_active == True:
<span class="sr">, current location</span>
%endif
% if tab.has_img == True:
<img src="${tab.img}"/>
%endif

View File

@@ -198,7 +198,8 @@ function goto( mode)
<H2>Student-specific grade inspection and adjustment</h2>
<p>edX email address or their username: </p>
<p><input type="text" name="unique_student_identifier"> <input type="submit" name="action" value="Get link to student's progress page"></p>
<p>and, if you want to reset the number of attempts for a problem, the urlname of that problem</p>
<p>and, if you want to reset the number of attempts for a problem, the urlname of that problem
(e.g. if the location is <tt>i4x://university/course/problem/problemname</tt>, then the urlname is <tt>problemname</tt>).</p>
<p> <input type="text" name="problem_to_reset" size="60"> <input type="submit" name="action" value="Reset student's attempts"> </p>
%if instructor_access:

View File

@@ -35,7 +35,7 @@ ${progress_graph.body(grade_summary, course.grade_cutoffs, "grade-detail-graph",
</header>
%if not course.disable_progress_graph:
<div id="grade-detail-graph"></div>
<div id="grade-detail-graph" aria-hidden="true"></div>
%endif
<ol class="chapters">
@@ -54,7 +54,12 @@ ${progress_graph.body(grade_summary, course.grade_cutoffs, "grade-detail-graph",
%>
<h3><a href="${reverse('courseware_section', kwargs=dict(course_id=course.id, chapter=chapter['url_name'], section=section['url_name']))}">
${ section['display_name'] }</a>
${ section['display_name'] }
%if total > 0 or earned > 0:
<span class="sr">
${"{0:.3n} of {1:.3n} possible points".format( float(earned), float(total) )}
</span></a>
%endif
%if total > 0 or earned > 0:
<span> ${"({0:.3n}/{1:.3n}) {2}".format( float(earned), float(total), percentageString )}</span>
%endif

View File

@@ -29,7 +29,7 @@
<section class="problem-list-container">
<h2>Instructions</h2>
<div class="instructions">
<p>This is the list of problems that current need to be graded in order to train the machine learning models. Each problem needs to be trained separately, and we have indicated the number of student submissions that need to be graded in order for a model to be generated. You can grade more than the minimum required number of submissions--this will improve the accuracy of machine learning, though with diminishing returns. You can see the current accuracy of machine learning while grading.</p>
<p>This is the list of problems that currently need to be graded in order to train the machine learning models. Each problem needs to be trained separately, and we have indicated the number of student submissions that need to be graded in order for a model to be generated. You can grade more than the minimum required number of submissions--this will improve the accuracy of machine learning, though with diminishing returns. You can see the current accuracy of machine learning while grading.</p>
</div>
<h2>Problem List</h2>

View File

@@ -86,7 +86,7 @@
<section class="login container">
<section role="main" class="content">
<form role="form" id="login-form" method="post" data-remote="true" action="/login_ajax">
<form role="form" id="login-form" method="post" data-remote="true" action="/login_ajax" novalidate>
<!-- status messages -->
<div role="alert" class="status message">
@@ -111,11 +111,11 @@
<ol class="list-input">
<li class="field required text" id="field-email">
<label for="email">E-mail</label>
<input class="" id="email" type="email" name="email" value="" placeholder="example: username@domain.com" />
<input class="" id="email" type="email" name="email" value="" placeholder="example: username@domain.com" required aria-required="true" />
</li>
<li class="field required password" id="field-password">
<label for="password">Password</label>
<input id="password" type="password" name="password" value="" />
<input id="password" type="password" name="password" value="" required aria-required="true" />
<span class="tip tip-input">
<a href="#forgot-password-modal" rel="leanModal" class="pwd-reset action action-forgotpw">Forgot password?</a>
</span>

View File

@@ -65,11 +65,11 @@ site_status_msg = get_site_status_msg(course_id)
<li class="primary">
<a href="${reverse('dashboard')}" class="user-link">
<span class="avatar"></span>
${user.username}
<span class="sr">Dashboard for: </span> ${user.username}
</a>
</li>
<li class="primary">
<a href="#" class="dropdown">&#9662</a>
<a href="#" class="dropdown"><span class="sr">More options dropdown</span> &#9662</a>
<ul class="dropdown-menu">
<%block name="navigation_dropdown_menu_links" >
<li><a href="${marketing_link('FAQ')}">Help</a></li>

View File

@@ -10,19 +10,19 @@
${ problem['html'] }
<section class="action">
<input type="hidden" name="problem_id" value="${ problem['name'] }">
<input type="hidden" name="problem_id" value="${ problem['name'] }" />
% if check_button:
<input class="check ${ check_button }" type="button" value="${ check_button }">
<input class="check ${ check_button }" type="button" value="${ check_button }" />
% endif
% if reset_button:
<input class="reset" type="button" value="Reset">
<input class="reset" type="button" value="Reset" />
% endif
% if save_button:
<input class="save" type="button" value="Save">
<input class="save" type="button" value="Save" />
% endif
% if answer_available:
<input class="show" type="button" value="Show Answer">
<button class="show"><span class="show-label">Show Answer(s)</span> <span class="sr">(for question(s) above - adjacent to each field)</span></button>
% endif
% if attempts_allowed :
<section class="submission_feedback">

View File

@@ -89,7 +89,7 @@
<section class="register container">
<section role="main" class="content">
<form role="form" id="register-form" method="post" data-remote="true" action="/create_account">
<form role="form" id="register-form" method="post" data-remote="true" action="/create_account" novalidate>
<!-- status messages -->
<div role="alert" class="status message">
@@ -115,20 +115,20 @@
<ol class="list-input">
<li class="field required text" id="field-email">
<label for="email">E-mail</label>
<input class="" id="email" type="email" name="email" value="" placeholder="example: username@domain.com" />
<input class="" id="email" type="email" name="email" value="" placeholder="example: username@domain.com" required aria-required="true" />
</li>
<li class="field required password" id="field-password">
<label for="password">Password</label>
<input id="password" type="password" name="password" value="" />
<input id="password" type="password" name="password" value="" required aria-required="true" />
</li>
<li class="field required text" id="field-username">
<label for="username">Public Username</label>
<input id="username" type="text" name="username" value="" placeholder="example: JaneDoe" />
<input id="username" type="text" name="username" value="" placeholder="example: JaneDoe" required aria-required="true" />
<span class="tip tip-input">Will be shown in any discussions or forums you participate in</span>
</li>
<li class="field required text" id="field-name">
<label for="name">Full Name</label>
<input id="name" type="text" name="name" value="" placeholder="example: Jane Doe" />
<input id="name" type="text" name="name" value="" placeholder="example: Jane Doe" required aria-required="true" />
<span class="tip tip-input">Needed for any certificates you may earn <strong>(cannot be changed later)</strong></span>
</li>
</ol>
@@ -143,7 +143,7 @@
<ol class="list-input">
<li class="field required text" id="field-username">
<label for="username">Public Username</label>
<input id="username" type="text" name="username" value="${extauth_username}" placeholder="example: JaneDoe" />
<input id="username" type="text" name="username" value="${extauth_username}" placeholder="example: JaneDoe" required aria-required="true" />
<span class="tip tip-input">Will be shown in any discussions or forums you participate in</span>
</li>
</ol>
@@ -211,7 +211,7 @@
<ol class="list-input">
<li class="field-group">
<div class="field required checkbox" id="field-tos">
<input id="tos-yes" type="checkbox" name="terms_of_service" value="true" />
<input id="tos-yes" type="checkbox" name="terms_of_service" value="true" required aria-required="true" />
<label for="tos-yes">I agree to the <a href="${marketing_link('TOS')}" class="new-vp">Terms of Service</a></label>
</div>

View File

@@ -1,5 +1,9 @@
<div id="sequence_${element_id}" class="sequence" data-id="${item_id}" data-position="${position}" data-course_modx_root="/course/modx" >
<nav aria-label="Section Navigation" class="sequence-nav">
<ul class="sequence-nav-buttons">
<li class="prev"><a href="#">Previous</a></li>
</ul>
<div class="sequence-list-wrapper">
<ol id="sequence-list">
% for idx, item in enumerate(items):
@@ -20,7 +24,6 @@
</div>
<ul class="sequence-nav-buttons">
<li class="prev"><a href="#">Previous</a></li>
<li class="next"><a href="#">Next</a></li>
</ul>
</nav>

View File

@@ -18,6 +18,7 @@
data-start="${start}"
data-end="${end}"
data-caption-asset-path="${caption_asset_path}"
data-autoplay="${autoplay}"
>
<div class="tc-wrapper">
<article class="video-wrapper">

View File

@@ -1,7 +1,9 @@
% if settings.MITX_FEATURES.get('SEGMENT_IO_LMS'):
<!-- begin Segment.io -->
<script type="text/javascript">
// Leaving this line out of the feature flag block is intentional. Pulling the line outside of the if statement allows it to serve as its own dummy object.
var analytics=analytics||[];analytics.load=function(e){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src=("https:"===document.location.protocol?"https://":"http://")+"d2dq2ahtl5zl1z.cloudfront.net/analytics.js/v1/"+e+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(t,n);var r=function(e){return function(){analytics.push([e].concat(Array.prototype.slice.call(arguments,0)))}},i=["identify","track","trackLink","trackForm","trackClick","trackSubmit","pageview","ab","alias","ready"];for(var s=0;s<i.length;s++)analytics[i[s]]=r(i[s])};
% if settings.MITX_FEATURES.get('SEGMENT_IO_LMS'):
analytics.load("${ settings.SEGMENT_IO_LMS_KEY }");
% if user.is_authenticated():
@@ -11,14 +13,6 @@
});
% endif
% endif
</script>
<!-- end Segment.io -->
% else:
<!-- dummy segment.io -->
<script type="text/javascript">
var analytics = {
track: function() { return; }
};
</script>
<!-- end dummy segment.io -->
% endif

View File

@@ -98,6 +98,8 @@ if not settings.MITX_FEATURES["USE_CUSTOM_THEME"]:
url(r'^press$', 'student.views.press', name="press"),
url(r'^media-kit$', 'static_template_view.views.render',
{'template': 'media-kit.html'}, name="media-kit"),
url(r'^faq$', 'static_template_view.views.render',
{'template': 'faq.html'}, name="faq_edx"),
url(r'^help$', 'static_template_view.views.render',
{'template': 'help.html'}, name="help_edx"),
@@ -125,7 +127,7 @@ for key, value in settings.MKTG_URL_LINK_MAP.items():
continue
# These urls are enabled separately
if key == "ROOT" or key == "COURSES":
if key == "ROOT" or key == "COURSES" or key == "FAQ":
continue
# Make the assumptions that the templates are all in the same dir

View File

@@ -1,15 +1,15 @@
"""
Namespace that defines fields common to all blocks used in the LMS
"""
from xblock.core import Namespace, Boolean, Scope, String
from xmodule.fields import Date, Timedelta, StringyFloat, StringyBoolean
from xblock.core import Namespace, Boolean, Scope, String, Float
from xmodule.fields import Date, Timedelta
class LmsNamespace(Namespace):
"""
Namespace that defines fields common to all blocks used in the LMS
"""
hide_from_toc = StringyBoolean(
hide_from_toc = Boolean(
help="Whether to display this module in the table of contents",
default=False,
scope=Scope.settings
@@ -37,7 +37,7 @@ class LmsNamespace(Namespace):
)
showanswer = String(help="When to show the problem answer to the student", scope=Scope.settings, default="closed")
rerandomize = String(help="When to rerandomize the problem", default="always", scope=Scope.settings)
days_early_for_beta = StringyFloat(
days_early_for_beta = Float(
help="Number of days early to show content to beta users",
default=None,
scope=Scope.settings