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:
@@ -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
|
||||
@@ -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:
|
||||
|
||||
25
lms/djangoapps/courseware/features/navigation.feature
Normal file
25
lms/djangoapps/courseware/features/navigation.feature
Normal 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"
|
||||
186
lms/djangoapps/courseware/features/navigation.py
Normal file
186
lms/djangoapps/courseware/features/navigation.py
Normal 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)
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
6
lms/djangoapps/courseware/features/videoalpha.feature
Normal file
6
lms/djangoapps/courseware/features/videoalpha.feature
Normal 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
|
||||
36
lms/djangoapps/courseware/features/videoalpha.py
Normal file
36
lms/djangoapps/courseware/features/videoalpha.py
Normal 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')
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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})
|
||||
54
lms/djangoapps/courseware/tests/test_videoalpha_mongo.py
Normal file
54
lms/djangoapps/courseware/tests/test_videoalpha_mongo.py
Normal 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)
|
||||
129
lms/djangoapps/courseware/tests/test_videoalpha_xml.py
Normal file
129
lms/djangoapps/courseware/tests/test_videoalpha_xml.py
Normal 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})
|
||||
@@ -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,
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -11,6 +11,7 @@ from markdown import markdown
|
||||
|
||||
from .wiki_settings import *
|
||||
from util.cache import cache
|
||||
from pytz import UTC
|
||||
|
||||
|
||||
class ShouldHaveExactlyOneRootSlug(Exception):
|
||||
@@ -265,7 +266,7 @@ class Revision(models.Model):
|
||||
return
|
||||
else:
|
||||
import datetime
|
||||
self.article.modified_on = datetime.datetime.now()
|
||||
self.article.modified_on = datetime.datetime.now(UTC)
|
||||
self.article.save()
|
||||
|
||||
# Increment counter according to previous revision
|
||||
|
||||
@@ -24,7 +24,7 @@ modulestore_options = {
|
||||
'db': 'test_xmodule',
|
||||
'collection': 'acceptance_modulestore',
|
||||
'fs_root': TEST_ROOT / "data",
|
||||
'render_template': 'mitxmako.shortcuts.render_to_string',
|
||||
'render_template': 'mitxmako.shortcuts.render_to_string'
|
||||
}
|
||||
|
||||
MODULESTORE = {
|
||||
|
||||
@@ -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
24
lms/envs/aws_migrate.py
Normal 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
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ mark {
|
||||
}
|
||||
|
||||
.sr {
|
||||
@include text-sr();
|
||||
@extend .text-sr;
|
||||
}
|
||||
|
||||
.help-tab {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">▾</a>
|
||||
<a href="#" class="dropdown"><span class="sr">More options dropdown</span> ▾</a>
|
||||
<ul class="dropdown-menu">
|
||||
<%block name="navigation_dropdown_menu_links" >
|
||||
<li><a href="${marketing_link('FAQ')}">Help</a></li>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user