Merge pull request #1429 from MITx/feature/cale/pep8-autofix
Feature/cale/pep8 autofix
This commit is contained in:
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ STAFF_ROLE_NAME = 'staff'
|
|||||||
# we're just making a Django group for each location/role combo
|
# we're just making a Django group for each location/role combo
|
||||||
# to do this we're just creating a Group name which is a formatted string
|
# to do this we're just creating a Group name which is a formatted string
|
||||||
# of those two variables
|
# of those two variables
|
||||||
|
|
||||||
|
|
||||||
def get_course_groupname_for_role(location, role):
|
def get_course_groupname_for_role(location, role):
|
||||||
loc = Location(location)
|
loc = Location(location)
|
||||||
# hack: check for existence of a group name in the legacy LMS format <role>_<course>
|
# hack: check for existence of a group name in the legacy LMS format <role>_<course>
|
||||||
@@ -25,11 +27,12 @@ def get_course_groupname_for_role(location, role):
|
|||||||
# more information
|
# more information
|
||||||
groupname = '{0}_{1}'.format(role, loc.course)
|
groupname = '{0}_{1}'.format(role, loc.course)
|
||||||
|
|
||||||
if len(Group.objects.filter(name = groupname)) == 0:
|
if len(Group.objects.filter(name=groupname)) == 0:
|
||||||
groupname = '{0}_{1}'.format(role,loc.course_id)
|
groupname = '{0}_{1}'.format(role, loc.course_id)
|
||||||
|
|
||||||
return groupname
|
return groupname
|
||||||
|
|
||||||
|
|
||||||
def get_users_in_course_group_by_role(location, role):
|
def get_users_in_course_group_by_role(location, role):
|
||||||
groupname = get_course_groupname_for_role(location, role)
|
groupname = get_course_groupname_for_role(location, role)
|
||||||
(group, created) = Group.objects.get_or_create(name=groupname)
|
(group, created) = Group.objects.get_or_create(name=groupname)
|
||||||
@@ -39,6 +42,8 @@ def get_users_in_course_group_by_role(location, role):
|
|||||||
'''
|
'''
|
||||||
Create all permission groups for a new course and subscribe the caller into those roles
|
Create all permission groups for a new course and subscribe the caller into those roles
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
def create_all_course_groups(creator, location):
|
def create_all_course_groups(creator, location):
|
||||||
create_new_course_group(creator, location, INSTRUCTOR_ROLE_NAME)
|
create_new_course_group(creator, location, INSTRUCTOR_ROLE_NAME)
|
||||||
create_new_course_group(creator, location, STAFF_ROLE_NAME)
|
create_new_course_group(creator, location, STAFF_ROLE_NAME)
|
||||||
@@ -46,7 +51,7 @@ def create_all_course_groups(creator, location):
|
|||||||
|
|
||||||
def create_new_course_group(creator, location, role):
|
def create_new_course_group(creator, location, role):
|
||||||
groupname = get_course_groupname_for_role(location, role)
|
groupname = get_course_groupname_for_role(location, role)
|
||||||
(group, created) =Group.objects.get_or_create(name=groupname)
|
(group, created) = Group.objects.get_or_create(name=groupname)
|
||||||
if created:
|
if created:
|
||||||
group.save()
|
group.save()
|
||||||
|
|
||||||
@@ -59,6 +64,8 @@ def create_new_course_group(creator, location, role):
|
|||||||
This is to be called only by either a command line code path or through a app which has already
|
This is to be called only by either a command line code path or through a app which has already
|
||||||
asserted permissions
|
asserted permissions
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
def _delete_course_group(location):
|
def _delete_course_group(location):
|
||||||
# remove all memberships
|
# remove all memberships
|
||||||
instructors = Group.objects.get(name=get_course_groupname_for_role(location, INSTRUCTOR_ROLE_NAME))
|
instructors = Group.objects.get(name=get_course_groupname_for_role(location, INSTRUCTOR_ROLE_NAME))
|
||||||
@@ -75,6 +82,8 @@ def _delete_course_group(location):
|
|||||||
This is to be called only by either a command line code path or through an app which has already
|
This is to be called only by either a command line code path or through an app which has already
|
||||||
asserted permissions to do this action
|
asserted permissions to do this action
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
def _copy_course_group(source, dest):
|
def _copy_course_group(source, dest):
|
||||||
instructors = Group.objects.get(name=get_course_groupname_for_role(source, INSTRUCTOR_ROLE_NAME))
|
instructors = Group.objects.get(name=get_course_groupname_for_role(source, INSTRUCTOR_ROLE_NAME))
|
||||||
new_instructors_group = Group.objects.get(name=get_course_groupname_for_role(dest, INSTRUCTOR_ROLE_NAME))
|
new_instructors_group = Group.objects.get(name=get_course_groupname_for_role(dest, INSTRUCTOR_ROLE_NAME))
|
||||||
@@ -133,8 +142,6 @@ def remove_user_from_course_group(caller, user, location, role):
|
|||||||
def is_user_in_course_group_role(user, location, role):
|
def is_user_in_course_group_role(user, location, role):
|
||||||
if user.is_active and user.is_authenticated:
|
if user.is_active and user.is_authenticated:
|
||||||
# all "is_staff" flagged accounts belong to all groups
|
# all "is_staff" flagged accounts belong to all groups
|
||||||
return user.is_staff or user.groups.filter(name=get_course_groupname_for_role(location,role)).count() > 0
|
return user.is_staff or user.groups.filter(name=get_course_groupname_for_role(location, role)).count() > 0
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import logging
|
|||||||
|
|
||||||
## TODO store as array of { date, content } and override course_info_module.definition_from_xml
|
## TODO store as array of { date, content } and override course_info_module.definition_from_xml
|
||||||
## This should be in a class which inherits from XmlDescriptor
|
## This should be in a class which inherits from XmlDescriptor
|
||||||
|
|
||||||
|
|
||||||
def get_course_updates(location):
|
def get_course_updates(location):
|
||||||
"""
|
"""
|
||||||
Retrieve the relevant course_info updates and unpack into the model which the client expects:
|
Retrieve the relevant course_info updates and unpack into the model which the client expects:
|
||||||
@@ -42,12 +44,13 @@ def get_course_updates(location):
|
|||||||
content = "\n".join([html.tostring(ele) for ele in update[1:]])
|
content = "\n".join([html.tostring(ele) for ele in update[1:]])
|
||||||
|
|
||||||
# make the id on the client be 1..len w/ 1 being the oldest and len being the newest
|
# make the id on the client be 1..len w/ 1 being the oldest and len being the newest
|
||||||
course_upd_collection.append({"id" : location_base + "/" + str(len(course_html_parsed) - idx),
|
course_upd_collection.append({"id": location_base + "/" + str(len(course_html_parsed) - idx),
|
||||||
"date" : update.findtext("h2"),
|
"date": update.findtext("h2"),
|
||||||
"content" : content})
|
"content": content})
|
||||||
|
|
||||||
return course_upd_collection
|
return course_upd_collection
|
||||||
|
|
||||||
|
|
||||||
def update_course_updates(location, update, passed_id=None):
|
def update_course_updates(location, update, passed_id=None):
|
||||||
"""
|
"""
|
||||||
Either add or update the given course update. It will add it if the passed_id is absent or None. It will update it if
|
Either add or update the given course update. It will add it if the passed_id is absent or None. It will update it if
|
||||||
@@ -85,9 +88,10 @@ def update_course_updates(location, update, passed_id=None):
|
|||||||
course_updates.definition['data'] = html.tostring(course_html_parsed)
|
course_updates.definition['data'] = html.tostring(course_html_parsed)
|
||||||
modulestore('direct').update_item(location, course_updates.definition['data'])
|
modulestore('direct').update_item(location, course_updates.definition['data'])
|
||||||
|
|
||||||
return {"id" : passed_id,
|
return {"id": passed_id,
|
||||||
"date" : update['date'],
|
"date": update['date'],
|
||||||
"content" :update['content']}
|
"content": update['content']}
|
||||||
|
|
||||||
|
|
||||||
def delete_course_update(location, update, passed_id):
|
def delete_course_update(location, update, passed_id):
|
||||||
"""
|
"""
|
||||||
@@ -124,6 +128,7 @@ def delete_course_update(location, update, passed_id):
|
|||||||
|
|
||||||
return get_course_updates(location)
|
return get_course_updates(location)
|
||||||
|
|
||||||
|
|
||||||
def get_idx(passed_id):
|
def get_idx(passed_id):
|
||||||
"""
|
"""
|
||||||
From the url w/ idx appended, get the idx.
|
From the url w/ idx appended, get the idx.
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ from logging import getLogger
|
|||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
########### STEP HELPERS ##############
|
########### STEP HELPERS ##############
|
||||||
|
|
||||||
|
|
||||||
@step('I (?:visit|access|open) the Studio homepage$')
|
@step('I (?:visit|access|open) the Studio homepage$')
|
||||||
def i_visit_the_studio_homepage(step):
|
def i_visit_the_studio_homepage(step):
|
||||||
# To make this go to port 8001, put
|
# To make this go to port 8001, put
|
||||||
@@ -20,25 +22,30 @@ def i_visit_the_studio_homepage(step):
|
|||||||
world.browser.visit(django_url('/'))
|
world.browser.visit(django_url('/'))
|
||||||
assert world.browser.is_element_present_by_css('body.no-header', 10)
|
assert world.browser.is_element_present_by_css('body.no-header', 10)
|
||||||
|
|
||||||
|
|
||||||
@step('I am logged into Studio$')
|
@step('I am logged into Studio$')
|
||||||
def i_am_logged_into_studio(step):
|
def i_am_logged_into_studio(step):
|
||||||
log_into_studio()
|
log_into_studio()
|
||||||
|
|
||||||
|
|
||||||
@step('I confirm the alert$')
|
@step('I confirm the alert$')
|
||||||
def i_confirm_with_ok(step):
|
def i_confirm_with_ok(step):
|
||||||
world.browser.get_alert().accept()
|
world.browser.get_alert().accept()
|
||||||
|
|
||||||
|
|
||||||
@step(u'I press the "([^"]*)" delete icon$')
|
@step(u'I press the "([^"]*)" delete icon$')
|
||||||
def i_press_the_category_delete_icon(step, category):
|
def i_press_the_category_delete_icon(step, category):
|
||||||
if category == 'section':
|
if category == 'section':
|
||||||
css = 'a.delete-button.delete-section-button span.delete-icon'
|
css = 'a.delete-button.delete-section-button span.delete-icon'
|
||||||
elif category == 'subsection':
|
elif category == 'subsection':
|
||||||
css='a.delete-button.delete-subsection-button span.delete-icon'
|
css = 'a.delete-button.delete-subsection-button span.delete-icon'
|
||||||
else:
|
else:
|
||||||
assert False, 'Invalid category: %s' % category
|
assert False, 'Invalid category: %s' % category
|
||||||
css_click(css)
|
css_click(css)
|
||||||
|
|
||||||
####### HELPER FUNCTIONS ##############
|
####### HELPER FUNCTIONS ##############
|
||||||
|
|
||||||
|
|
||||||
def create_studio_user(
|
def create_studio_user(
|
||||||
uname='robot',
|
uname='robot',
|
||||||
email='robot+studio@edx.org',
|
email='robot+studio@edx.org',
|
||||||
@@ -58,6 +65,7 @@ def create_studio_user(
|
|||||||
|
|
||||||
user_profile = UserProfileFactory(user=studio_user)
|
user_profile = UserProfileFactory(user=studio_user)
|
||||||
|
|
||||||
|
|
||||||
def flush_xmodule_store():
|
def flush_xmodule_store():
|
||||||
# Flush and initialize the module store
|
# Flush and initialize the module store
|
||||||
# It needs the templates because it creates new records
|
# It needs the templates because it creates new records
|
||||||
@@ -70,26 +78,32 @@ def flush_xmodule_store():
|
|||||||
xmodule.modulestore.django.modulestore().collection.drop()
|
xmodule.modulestore.django.modulestore().collection.drop()
|
||||||
xmodule.templates.update_templates()
|
xmodule.templates.update_templates()
|
||||||
|
|
||||||
def assert_css_with_text(css,text):
|
|
||||||
|
def assert_css_with_text(css, text):
|
||||||
assert_true(world.browser.is_element_present_by_css(css, 5))
|
assert_true(world.browser.is_element_present_by_css(css, 5))
|
||||||
assert_equal(world.browser.find_by_css(css).text, text)
|
assert_equal(world.browser.find_by_css(css).text, text)
|
||||||
|
|
||||||
|
|
||||||
def css_click(css):
|
def css_click(css):
|
||||||
world.browser.find_by_css(css).first.click()
|
world.browser.find_by_css(css).first.click()
|
||||||
|
|
||||||
|
|
||||||
def css_fill(css, value):
|
def css_fill(css, value):
|
||||||
world.browser.find_by_css(css).first.fill(value)
|
world.browser.find_by_css(css).first.fill(value)
|
||||||
|
|
||||||
|
|
||||||
def clear_courses():
|
def clear_courses():
|
||||||
flush_xmodule_store()
|
flush_xmodule_store()
|
||||||
|
|
||||||
|
|
||||||
def fill_in_course_info(
|
def fill_in_course_info(
|
||||||
name='Robot Super Course',
|
name='Robot Super Course',
|
||||||
org='MITx',
|
org='MITx',
|
||||||
num='101'):
|
num='101'):
|
||||||
css_fill('.new-course-name',name)
|
css_fill('.new-course-name', name)
|
||||||
css_fill('.new-course-org',org)
|
css_fill('.new-course-org', org)
|
||||||
css_fill('.new-course-number',num)
|
css_fill('.new-course-number', num)
|
||||||
|
|
||||||
|
|
||||||
def log_into_studio(
|
def log_into_studio(
|
||||||
uname='robot',
|
uname='robot',
|
||||||
@@ -108,20 +122,23 @@ def log_into_studio(
|
|||||||
|
|
||||||
assert_true(world.browser.is_element_present_by_css('.new-course-button', 5))
|
assert_true(world.browser.is_element_present_by_css('.new-course-button', 5))
|
||||||
|
|
||||||
|
|
||||||
def create_a_course():
|
def create_a_course():
|
||||||
css_click('a.new-course-button')
|
css_click('a.new-course-button')
|
||||||
fill_in_course_info()
|
fill_in_course_info()
|
||||||
css_click('input.new-course-save')
|
css_click('input.new-course-save')
|
||||||
assert_true(world.browser.is_element_present_by_css('a#courseware-tab', 5))
|
assert_true(world.browser.is_element_present_by_css('a#courseware-tab', 5))
|
||||||
|
|
||||||
|
|
||||||
def add_section(name='My Section'):
|
def add_section(name='My Section'):
|
||||||
link_css = 'a.new-courseware-section-button'
|
link_css = 'a.new-courseware-section-button'
|
||||||
css_click(link_css)
|
css_click(link_css)
|
||||||
name_css = '.new-section-name'
|
name_css = '.new-section-name'
|
||||||
save_css = '.new-section-name-save'
|
save_css = '.new-section-name-save'
|
||||||
css_fill(name_css,name)
|
css_fill(name_css, name)
|
||||||
css_click(save_css)
|
css_click(save_css)
|
||||||
|
|
||||||
|
|
||||||
def add_subsection(name='Subsection One'):
|
def add_subsection(name='Subsection One'):
|
||||||
css = 'a.new-subsection-item'
|
css = 'a.new-subsection-item'
|
||||||
css_click(css)
|
css_click(css)
|
||||||
|
|||||||
@@ -2,49 +2,61 @@ from lettuce import world, step
|
|||||||
from common import *
|
from common import *
|
||||||
|
|
||||||
############### ACTIONS ####################
|
############### ACTIONS ####################
|
||||||
|
|
||||||
|
|
||||||
@step('There are no courses$')
|
@step('There are no courses$')
|
||||||
def no_courses(step):
|
def no_courses(step):
|
||||||
clear_courses()
|
clear_courses()
|
||||||
|
|
||||||
|
|
||||||
@step('I click the New Course button$')
|
@step('I click the New Course button$')
|
||||||
def i_click_new_course(step):
|
def i_click_new_course(step):
|
||||||
css_click('.new-course-button')
|
css_click('.new-course-button')
|
||||||
|
|
||||||
|
|
||||||
@step('I fill in the new course information$')
|
@step('I fill in the new course information$')
|
||||||
def i_fill_in_a_new_course_information(step):
|
def i_fill_in_a_new_course_information(step):
|
||||||
fill_in_course_info()
|
fill_in_course_info()
|
||||||
|
|
||||||
|
|
||||||
@step('I create a new course$')
|
@step('I create a new course$')
|
||||||
def i_create_a_course(step):
|
def i_create_a_course(step):
|
||||||
create_a_course()
|
create_a_course()
|
||||||
|
|
||||||
|
|
||||||
@step('I click the course link in My Courses$')
|
@step('I click the course link in My Courses$')
|
||||||
def i_click_the_course_link_in_my_courses(step):
|
def i_click_the_course_link_in_my_courses(step):
|
||||||
course_css = 'span.class-name'
|
course_css = 'span.class-name'
|
||||||
css_click(course_css)
|
css_click(course_css)
|
||||||
|
|
||||||
############ ASSERTIONS ###################
|
############ ASSERTIONS ###################
|
||||||
|
|
||||||
|
|
||||||
@step('the Courseware page has loaded in Studio$')
|
@step('the Courseware page has loaded in Studio$')
|
||||||
def courseware_page_has_loaded_in_studio(step):
|
def courseware_page_has_loaded_in_studio(step):
|
||||||
courseware_css = 'a#courseware-tab'
|
courseware_css = 'a#courseware-tab'
|
||||||
assert world.browser.is_element_present_by_css(courseware_css)
|
assert world.browser.is_element_present_by_css(courseware_css)
|
||||||
|
|
||||||
|
|
||||||
@step('I see the course listed in My Courses$')
|
@step('I see the course listed in My Courses$')
|
||||||
def i_see_the_course_in_my_courses(step):
|
def i_see_the_course_in_my_courses(step):
|
||||||
course_css = 'span.class-name'
|
course_css = 'span.class-name'
|
||||||
assert_css_with_text(course_css,'Robot Super Course')
|
assert_css_with_text(course_css, 'Robot Super Course')
|
||||||
|
|
||||||
|
|
||||||
@step('the course is loaded$')
|
@step('the course is loaded$')
|
||||||
def course_is_loaded(step):
|
def course_is_loaded(step):
|
||||||
class_css = 'a.class-name'
|
class_css = 'a.class-name'
|
||||||
assert_css_with_text(class_css,'Robot Super Course')
|
assert_css_with_text(class_css, 'Robot Super Course')
|
||||||
|
|
||||||
|
|
||||||
@step('I am on the "([^"]*)" tab$')
|
@step('I am on the "([^"]*)" tab$')
|
||||||
def i_am_on_tab(step, tab_name):
|
def i_am_on_tab(step, tab_name):
|
||||||
header_css = 'div.inner-wrapper h1'
|
header_css = 'div.inner-wrapper h1'
|
||||||
assert_css_with_text(header_css,tab_name)
|
assert_css_with_text(header_css, tab_name)
|
||||||
|
|
||||||
|
|
||||||
@step('I see a link for adding a new section$')
|
@step('I see a link for adding a new section$')
|
||||||
def i_see_new_section_link(step):
|
def i_see_new_section_link(step):
|
||||||
link_css = 'a.new-courseware-section-button'
|
link_css = 'a.new-courseware-section-button'
|
||||||
assert_css_with_text(link_css,'New Section')
|
assert_css_with_text(link_css, 'New Section')
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from student.models import User, UserProfile, Registration
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
class UserProfileFactory(factory.Factory):
|
class UserProfileFactory(factory.Factory):
|
||||||
FACTORY_FOR = UserProfile
|
FACTORY_FOR = UserProfile
|
||||||
|
|
||||||
@@ -10,12 +11,14 @@ class UserProfileFactory(factory.Factory):
|
|||||||
name = 'Robot Studio'
|
name = 'Robot Studio'
|
||||||
courseware = 'course.xml'
|
courseware = 'course.xml'
|
||||||
|
|
||||||
|
|
||||||
class RegistrationFactory(factory.Factory):
|
class RegistrationFactory(factory.Factory):
|
||||||
FACTORY_FOR = Registration
|
FACTORY_FOR = Registration
|
||||||
|
|
||||||
user = None
|
user = None
|
||||||
activation_key = uuid.uuid4().hex
|
activation_key = uuid.uuid4().hex
|
||||||
|
|
||||||
|
|
||||||
class UserFactory(factory.Factory):
|
class UserFactory(factory.Factory):
|
||||||
FACTORY_FOR = User
|
FACTORY_FOR = User
|
||||||
|
|
||||||
|
|||||||
@@ -2,54 +2,65 @@ from lettuce import world, step
|
|||||||
from common import *
|
from common import *
|
||||||
|
|
||||||
############### ACTIONS ####################
|
############### ACTIONS ####################
|
||||||
|
|
||||||
|
|
||||||
@step('I have opened a new course in Studio$')
|
@step('I have opened a new course in Studio$')
|
||||||
def i_have_opened_a_new_course(step):
|
def i_have_opened_a_new_course(step):
|
||||||
clear_courses()
|
clear_courses()
|
||||||
log_into_studio()
|
log_into_studio()
|
||||||
create_a_course()
|
create_a_course()
|
||||||
|
|
||||||
|
|
||||||
@step('I click the new section link$')
|
@step('I click the new section link$')
|
||||||
def i_click_new_section_link(step):
|
def i_click_new_section_link(step):
|
||||||
link_css = 'a.new-courseware-section-button'
|
link_css = 'a.new-courseware-section-button'
|
||||||
css_click(link_css)
|
css_click(link_css)
|
||||||
|
|
||||||
|
|
||||||
@step('I enter the section name and click save$')
|
@step('I enter the section name and click save$')
|
||||||
def i_save_section_name(step):
|
def i_save_section_name(step):
|
||||||
name_css = '.new-section-name'
|
name_css = '.new-section-name'
|
||||||
save_css = '.new-section-name-save'
|
save_css = '.new-section-name-save'
|
||||||
css_fill(name_css,'My Section')
|
css_fill(name_css, 'My Section')
|
||||||
css_click(save_css)
|
css_click(save_css)
|
||||||
|
|
||||||
|
|
||||||
@step('I have added a new section$')
|
@step('I have added a new section$')
|
||||||
def i_have_added_new_section(step):
|
def i_have_added_new_section(step):
|
||||||
add_section()
|
add_section()
|
||||||
|
|
||||||
|
|
||||||
@step('I click the Edit link for the release date$')
|
@step('I click the Edit link for the release date$')
|
||||||
def i_click_the_edit_link_for_the_release_date(step):
|
def i_click_the_edit_link_for_the_release_date(step):
|
||||||
button_css = 'div.section-published-date a.edit-button'
|
button_css = 'div.section-published-date a.edit-button'
|
||||||
css_click(button_css)
|
css_click(button_css)
|
||||||
|
|
||||||
|
|
||||||
@step('I save a new section release date$')
|
@step('I save a new section release date$')
|
||||||
def i_save_a_new_section_release_date(step):
|
def i_save_a_new_section_release_date(step):
|
||||||
date_css = 'input.start-date.date.hasDatepicker'
|
date_css = 'input.start-date.date.hasDatepicker'
|
||||||
time_css = 'input.start-time.time.ui-timepicker-input'
|
time_css = 'input.start-time.time.ui-timepicker-input'
|
||||||
css_fill(date_css,'12/25/2013')
|
css_fill(date_css, '12/25/2013')
|
||||||
# click here to make the calendar go away
|
# click here to make the calendar go away
|
||||||
css_click(time_css)
|
css_click(time_css)
|
||||||
css_fill(time_css,'12:00am')
|
css_fill(time_css, '12:00am')
|
||||||
css_click('a.save-button')
|
css_click('a.save-button')
|
||||||
|
|
||||||
############ ASSERTIONS ###################
|
############ ASSERTIONS ###################
|
||||||
|
|
||||||
|
|
||||||
@step('I see my section on the Courseware page$')
|
@step('I see my section on the Courseware page$')
|
||||||
def i_see_my_section_on_the_courseware_page(step):
|
def i_see_my_section_on_the_courseware_page(step):
|
||||||
section_css = 'span.section-name-span'
|
section_css = 'span.section-name-span'
|
||||||
assert_css_with_text(section_css,'My Section')
|
assert_css_with_text(section_css, 'My Section')
|
||||||
|
|
||||||
|
|
||||||
@step('the section does not exist$')
|
@step('the section does not exist$')
|
||||||
def section_does_not_exist(step):
|
def section_does_not_exist(step):
|
||||||
css = 'span.section-name-span'
|
css = 'span.section-name-span'
|
||||||
assert world.browser.is_element_not_present_by_css(css)
|
assert world.browser.is_element_not_present_by_css(css)
|
||||||
|
|
||||||
|
|
||||||
@step('I see a release date for my section$')
|
@step('I see a release date for my section$')
|
||||||
def i_see_a_release_date_for_my_section(step):
|
def i_see_a_release_date_for_my_section(step):
|
||||||
import re
|
import re
|
||||||
@@ -63,18 +74,21 @@ def i_see_a_release_date_for_my_section(step):
|
|||||||
date_regex = '[01][0-9]\/[0-3][0-9]\/[12][0-9][0-9][0-9]'
|
date_regex = '[01][0-9]\/[0-3][0-9]\/[12][0-9][0-9][0-9]'
|
||||||
time_regex = '[0-2][0-9]:[0-5][0-9]'
|
time_regex = '[0-2][0-9]:[0-5][0-9]'
|
||||||
match_string = '%s %s at %s' % (msg, date_regex, time_regex)
|
match_string = '%s %s at %s' % (msg, date_regex, time_regex)
|
||||||
assert re.match(match_string,status_text)
|
assert re.match(match_string, status_text)
|
||||||
|
|
||||||
|
|
||||||
@step('I see a link to create a new subsection$')
|
@step('I see a link to create a new subsection$')
|
||||||
def i_see_a_link_to_create_a_new_subsection(step):
|
def i_see_a_link_to_create_a_new_subsection(step):
|
||||||
css = 'a.new-subsection-item'
|
css = 'a.new-subsection-item'
|
||||||
assert world.browser.is_element_present_by_css(css)
|
assert world.browser.is_element_present_by_css(css)
|
||||||
|
|
||||||
|
|
||||||
@step('the section release date picker is not visible$')
|
@step('the section release date picker is not visible$')
|
||||||
def the_section_release_date_picker_not_visible(step):
|
def the_section_release_date_picker_not_visible(step):
|
||||||
css = 'div.edit-subsection-publish-settings'
|
css = 'div.edit-subsection-publish-settings'
|
||||||
assert False, world.browser.find_by_css(css).visible
|
assert False, world.browser.find_by_css(css).visible
|
||||||
|
|
||||||
|
|
||||||
@step('the section release date is updated$')
|
@step('the section release date is updated$')
|
||||||
def the_section_release_date_is_updated(step):
|
def the_section_release_date_is_updated(step):
|
||||||
css = 'span.published-status'
|
css = 'span.published-status'
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from lettuce import world, step
|
from lettuce import world, step
|
||||||
|
|
||||||
|
|
||||||
@step('I fill in the registration form$')
|
@step('I fill in the registration form$')
|
||||||
def i_fill_in_the_registration_form(step):
|
def i_fill_in_the_registration_form(step):
|
||||||
register_form = world.browser.find_by_css('form#register_form')
|
register_form = world.browser.find_by_css('form#register_form')
|
||||||
@@ -9,15 +10,18 @@ def i_fill_in_the_registration_form(step):
|
|||||||
register_form.find_by_name('name').fill('Robot Studio')
|
register_form.find_by_name('name').fill('Robot Studio')
|
||||||
register_form.find_by_name('terms_of_service').check()
|
register_form.find_by_name('terms_of_service').check()
|
||||||
|
|
||||||
|
|
||||||
@step('I press the "([^"]*)" button on the registration form$')
|
@step('I press the "([^"]*)" button on the registration form$')
|
||||||
def i_press_the_button_on_the_registration_form(step, button):
|
def i_press_the_button_on_the_registration_form(step, button):
|
||||||
register_form = world.browser.find_by_css('form#register_form')
|
register_form = world.browser.find_by_css('form#register_form')
|
||||||
register_form.find_by_value(button).click()
|
register_form.find_by_value(button).click()
|
||||||
|
|
||||||
|
|
||||||
@step('I should see be on the studio home page$')
|
@step('I should see be on the studio home page$')
|
||||||
def i_should_see_be_on_the_studio_home_page(step):
|
def i_should_see_be_on_the_studio_home_page(step):
|
||||||
assert world.browser.find_by_css('div.inner-wrapper')
|
assert world.browser.find_by_css('div.inner-wrapper')
|
||||||
|
|
||||||
|
|
||||||
@step(u'I should see the message "([^"]*)"$')
|
@step(u'I should see the message "([^"]*)"$')
|
||||||
def i_should_see_the_message(step, msg):
|
def i_should_see_the_message(step, msg):
|
||||||
assert world.browser.is_text_present(msg, 5)
|
assert world.browser.is_text_present(msg, 5)
|
||||||
@@ -6,11 +6,13 @@ from nose.tools import assert_true, assert_false, assert_equal
|
|||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@step(u'I have a course with no sections$')
|
@step(u'I have a course with no sections$')
|
||||||
def have_a_course(step):
|
def have_a_course(step):
|
||||||
clear_courses()
|
clear_courses()
|
||||||
course = CourseFactory.create()
|
course = CourseFactory.create()
|
||||||
|
|
||||||
|
|
||||||
@step(u'I have a course with 1 section$')
|
@step(u'I have a course with 1 section$')
|
||||||
def have_a_course_with_1_section(step):
|
def have_a_course_with_1_section(step):
|
||||||
clear_courses()
|
clear_courses()
|
||||||
@@ -18,8 +20,9 @@ def have_a_course_with_1_section(step):
|
|||||||
section = ItemFactory.create(parent_location=course.location)
|
section = ItemFactory.create(parent_location=course.location)
|
||||||
subsection1 = ItemFactory.create(
|
subsection1 = ItemFactory.create(
|
||||||
parent_location=section.location,
|
parent_location=section.location,
|
||||||
template = 'i4x://edx/templates/sequential/Empty',
|
template='i4x://edx/templates/sequential/Empty',
|
||||||
display_name = 'Subsection One',)
|
display_name='Subsection One',)
|
||||||
|
|
||||||
|
|
||||||
@step(u'I have a course with multiple sections$')
|
@step(u'I have a course with multiple sections$')
|
||||||
def have_a_course_with_two_sections(step):
|
def have_a_course_with_two_sections(step):
|
||||||
@@ -28,19 +31,20 @@ def have_a_course_with_two_sections(step):
|
|||||||
section = ItemFactory.create(parent_location=course.location)
|
section = ItemFactory.create(parent_location=course.location)
|
||||||
subsection1 = ItemFactory.create(
|
subsection1 = ItemFactory.create(
|
||||||
parent_location=section.location,
|
parent_location=section.location,
|
||||||
template = 'i4x://edx/templates/sequential/Empty',
|
template='i4x://edx/templates/sequential/Empty',
|
||||||
display_name = 'Subsection One',)
|
display_name='Subsection One',)
|
||||||
section2 = ItemFactory.create(
|
section2 = ItemFactory.create(
|
||||||
parent_location=course.location,
|
parent_location=course.location,
|
||||||
display_name='Section Two',)
|
display_name='Section Two',)
|
||||||
subsection2 = ItemFactory.create(
|
subsection2 = ItemFactory.create(
|
||||||
parent_location=section2.location,
|
parent_location=section2.location,
|
||||||
template = 'i4x://edx/templates/sequential/Empty',
|
template='i4x://edx/templates/sequential/Empty',
|
||||||
display_name = 'Subsection Alpha',)
|
display_name='Subsection Alpha',)
|
||||||
subsection3 = ItemFactory.create(
|
subsection3 = ItemFactory.create(
|
||||||
parent_location=section2.location,
|
parent_location=section2.location,
|
||||||
template = 'i4x://edx/templates/sequential/Empty',
|
template='i4x://edx/templates/sequential/Empty',
|
||||||
display_name = 'Subsection Beta',)
|
display_name='Subsection Beta',)
|
||||||
|
|
||||||
|
|
||||||
@step(u'I navigate to the course overview page$')
|
@step(u'I navigate to the course overview page$')
|
||||||
def navigate_to_the_course_overview_page(step):
|
def navigate_to_the_course_overview_page(step):
|
||||||
@@ -48,15 +52,18 @@ def navigate_to_the_course_overview_page(step):
|
|||||||
course_locator = '.class-name'
|
course_locator = '.class-name'
|
||||||
css_click(course_locator)
|
css_click(course_locator)
|
||||||
|
|
||||||
|
|
||||||
@step(u'I navigate to the courseware page of a course with multiple sections')
|
@step(u'I navigate to the courseware page of a course with multiple sections')
|
||||||
def nav_to_the_courseware_page_of_a_course_with_multiple_sections(step):
|
def nav_to_the_courseware_page_of_a_course_with_multiple_sections(step):
|
||||||
step.given('I have a course with multiple sections')
|
step.given('I have a course with multiple sections')
|
||||||
step.given('I navigate to the course overview page')
|
step.given('I navigate to the course overview page')
|
||||||
|
|
||||||
|
|
||||||
@step(u'I add a section')
|
@step(u'I add a section')
|
||||||
def i_add_a_section(step):
|
def i_add_a_section(step):
|
||||||
add_section(name='My New Section That I Just Added')
|
add_section(name='My New Section That I Just Added')
|
||||||
|
|
||||||
|
|
||||||
@step(u'I click the "([^"]*)" link$')
|
@step(u'I click the "([^"]*)" link$')
|
||||||
def i_click_the_text_span(step, text):
|
def i_click_the_text_span(step, text):
|
||||||
span_locator = '.toggle-button-sections span'
|
span_locator = '.toggle-button-sections span'
|
||||||
@@ -65,16 +72,19 @@ def i_click_the_text_span(step, text):
|
|||||||
assert_equal(world.browser.find_by_css(span_locator).value, text)
|
assert_equal(world.browser.find_by_css(span_locator).value, text)
|
||||||
css_click(span_locator)
|
css_click(span_locator)
|
||||||
|
|
||||||
|
|
||||||
@step(u'I collapse the first section$')
|
@step(u'I collapse the first section$')
|
||||||
def i_collapse_a_section(step):
|
def i_collapse_a_section(step):
|
||||||
collapse_locator = 'section.courseware-section a.collapse'
|
collapse_locator = 'section.courseware-section a.collapse'
|
||||||
css_click(collapse_locator)
|
css_click(collapse_locator)
|
||||||
|
|
||||||
|
|
||||||
@step(u'I expand the first section$')
|
@step(u'I expand the first section$')
|
||||||
def i_expand_a_section(step):
|
def i_expand_a_section(step):
|
||||||
expand_locator = 'section.courseware-section a.expand'
|
expand_locator = 'section.courseware-section a.expand'
|
||||||
css_click(expand_locator)
|
css_click(expand_locator)
|
||||||
|
|
||||||
|
|
||||||
@step(u'I see the "([^"]*)" link$')
|
@step(u'I see the "([^"]*)" link$')
|
||||||
def i_see_the_span_with_text(step, text):
|
def i_see_the_span_with_text(step, text):
|
||||||
span_locator = '.toggle-button-sections span'
|
span_locator = '.toggle-button-sections span'
|
||||||
@@ -82,6 +92,7 @@ def i_see_the_span_with_text(step, text):
|
|||||||
assert_equal(world.browser.find_by_css(span_locator).value, text)
|
assert_equal(world.browser.find_by_css(span_locator).value, text)
|
||||||
assert_true(world.browser.find_by_css(span_locator).visible)
|
assert_true(world.browser.find_by_css(span_locator).visible)
|
||||||
|
|
||||||
|
|
||||||
@step(u'I do not see the "([^"]*)" link$')
|
@step(u'I do not see the "([^"]*)" link$')
|
||||||
def i_do_not_see_the_span_with_text(step, text):
|
def i_do_not_see_the_span_with_text(step, text):
|
||||||
# Note that the span will exist on the page but not be visible
|
# Note that the span will exist on the page but not be visible
|
||||||
@@ -89,6 +100,7 @@ def i_do_not_see_the_span_with_text(step, text):
|
|||||||
assert_true(world.browser.is_element_present_by_css(span_locator))
|
assert_true(world.browser.is_element_present_by_css(span_locator))
|
||||||
assert_false(world.browser.find_by_css(span_locator).visible)
|
assert_false(world.browser.find_by_css(span_locator).visible)
|
||||||
|
|
||||||
|
|
||||||
@step(u'all sections are expanded$')
|
@step(u'all sections are expanded$')
|
||||||
def all_sections_are_expanded(step):
|
def all_sections_are_expanded(step):
|
||||||
subsection_locator = 'div.subsection-list'
|
subsection_locator = 'div.subsection-list'
|
||||||
@@ -96,6 +108,7 @@ def all_sections_are_expanded(step):
|
|||||||
for s in subsections:
|
for s in subsections:
|
||||||
assert_true(s.visible)
|
assert_true(s.visible)
|
||||||
|
|
||||||
|
|
||||||
@step(u'all sections are collapsed$')
|
@step(u'all sections are collapsed$')
|
||||||
def all_sections_are_expanded(step):
|
def all_sections_are_expanded(step):
|
||||||
subsection_locator = 'div.subsection-list'
|
subsection_locator = 'div.subsection-list'
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ from lettuce import world, step
|
|||||||
from common import *
|
from common import *
|
||||||
|
|
||||||
############### ACTIONS ####################
|
############### ACTIONS ####################
|
||||||
|
|
||||||
|
|
||||||
@step('I have opened a new course section in Studio$')
|
@step('I have opened a new course section in Studio$')
|
||||||
def i_have_opened_a_new_course_section(step):
|
def i_have_opened_a_new_course_section(step):
|
||||||
clear_courses()
|
clear_courses()
|
||||||
@@ -9,29 +11,35 @@ def i_have_opened_a_new_course_section(step):
|
|||||||
create_a_course()
|
create_a_course()
|
||||||
add_section()
|
add_section()
|
||||||
|
|
||||||
|
|
||||||
@step('I click the New Subsection link')
|
@step('I click the New Subsection link')
|
||||||
def i_click_the_new_subsection_link(step):
|
def i_click_the_new_subsection_link(step):
|
||||||
css = 'a.new-subsection-item'
|
css = 'a.new-subsection-item'
|
||||||
css_click(css)
|
css_click(css)
|
||||||
|
|
||||||
|
|
||||||
@step('I enter the subsection name and click save$')
|
@step('I enter the subsection name and click save$')
|
||||||
def i_save_subsection_name(step):
|
def i_save_subsection_name(step):
|
||||||
name_css = 'input.new-subsection-name-input'
|
name_css = 'input.new-subsection-name-input'
|
||||||
save_css = 'input.new-subsection-name-save'
|
save_css = 'input.new-subsection-name-save'
|
||||||
css_fill(name_css,'Subsection One')
|
css_fill(name_css, 'Subsection One')
|
||||||
css_click(save_css)
|
css_click(save_css)
|
||||||
|
|
||||||
|
|
||||||
@step('I have added a new subsection$')
|
@step('I have added a new subsection$')
|
||||||
def i_have_added_a_new_subsection(step):
|
def i_have_added_a_new_subsection(step):
|
||||||
add_subsection()
|
add_subsection()
|
||||||
|
|
||||||
############ ASSERTIONS ###################
|
############ ASSERTIONS ###################
|
||||||
|
|
||||||
|
|
||||||
@step('I see my subsection on the Courseware page$')
|
@step('I see my subsection on the Courseware page$')
|
||||||
def i_see_my_subsection_on_the_courseware_page(step):
|
def i_see_my_subsection_on_the_courseware_page(step):
|
||||||
css = 'span.subsection-name'
|
css = 'span.subsection-name'
|
||||||
assert world.browser.is_element_present_by_css(css)
|
assert world.browser.is_element_present_by_css(css)
|
||||||
css = 'span.subsection-name-value'
|
css = 'span.subsection-name-value'
|
||||||
assert_css_with_text(css,'Subsection One')
|
assert_css_with_text(css, 'Subsection One')
|
||||||
|
|
||||||
|
|
||||||
@step('the subsection does not exist$')
|
@step('the subsection does not exist$')
|
||||||
def the_subsection_does_not_exist(step):
|
def the_subsection_does_not_exist(step):
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from auth.authz import _copy_course_group
|
|||||||
# To run from command line: rake cms:clone SOURCE_LOC=MITx/111/Foo1 DEST_LOC=MITx/135/Foo3
|
# To run from command line: rake cms:clone SOURCE_LOC=MITx/111/Foo1 DEST_LOC=MITx/135/Foo3
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = \
|
help = \
|
||||||
'''Clone a MongoDB backed course to another location'''
|
'''Clone a MongoDB backed course to another location'''
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from auth.authz import _delete_course_group
|
|||||||
# To run from command line: rake cms:delete_course LOC=MITx/111/Foo1
|
# To run from command line: rake cms:delete_course LOC=MITx/111/Foo1
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = \
|
help = \
|
||||||
'''Delete a MongoDB backed course'''
|
'''Delete a MongoDB backed course'''
|
||||||
@@ -35,6 +36,3 @@ class Command(BaseCommand):
|
|||||||
print 'removing User permissions from course....'
|
print 'removing User permissions from course....'
|
||||||
# in the django layer, we need to remove all the user permissions groups associated with this course
|
# in the django layer, we need to remove all the user permissions groups associated with this course
|
||||||
_delete_course_group(loc)
|
_delete_course_group(loc)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
def query_yes_no(question, default="yes"):
|
def query_yes_no(question, default="yes"):
|
||||||
"""Ask a yes/no question via raw_input() and return their answer.
|
"""Ask a yes/no question via raw_input() and return their answer.
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ from lxml import etree
|
|||||||
import re
|
import re
|
||||||
from django.http import HttpResponseBadRequest, Http404
|
from django.http import HttpResponseBadRequest, Http404
|
||||||
|
|
||||||
def get_module_info(store, location, parent_location = None, rewrite_static_links = False):
|
|
||||||
|
def get_module_info(store, location, parent_location=None, rewrite_static_links=False):
|
||||||
try:
|
try:
|
||||||
if location.revision is None:
|
if location.revision is None:
|
||||||
module = store.get_item(location)
|
module = store.get_item(location)
|
||||||
@@ -36,6 +37,7 @@ def get_module_info(store, location, parent_location = None, rewrite_static_link
|
|||||||
'metadata': module.metadata
|
'metadata': module.metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def set_module_info(store, location, post_data):
|
def set_module_info(store, location, post_data):
|
||||||
module = None
|
module = None
|
||||||
isNew = False
|
isNew = False
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from student.models import (User, UserProfile, Registration,
|
|||||||
CourseEnrollmentAllowed)
|
CourseEnrollmentAllowed)
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
|
|
||||||
|
|
||||||
class UserProfileFactory(Factory):
|
class UserProfileFactory(Factory):
|
||||||
FACTORY_FOR = UserProfile
|
FACTORY_FOR = UserProfile
|
||||||
|
|
||||||
@@ -12,12 +13,14 @@ class UserProfileFactory(Factory):
|
|||||||
name = 'Robot Studio'
|
name = 'Robot Studio'
|
||||||
courseware = 'course.xml'
|
courseware = 'course.xml'
|
||||||
|
|
||||||
|
|
||||||
class RegistrationFactory(Factory):
|
class RegistrationFactory(Factory):
|
||||||
FACTORY_FOR = Registration
|
FACTORY_FOR = Registration
|
||||||
|
|
||||||
user = None
|
user = None
|
||||||
activation_key = uuid4().hex
|
activation_key = uuid4().hex
|
||||||
|
|
||||||
|
|
||||||
class UserFactory(Factory):
|
class UserFactory(Factory):
|
||||||
FACTORY_FOR = User
|
FACTORY_FOR = User
|
||||||
|
|
||||||
@@ -32,11 +35,13 @@ class UserFactory(Factory):
|
|||||||
last_login = datetime.now()
|
last_login = datetime.now()
|
||||||
date_joined = datetime.now()
|
date_joined = datetime.now()
|
||||||
|
|
||||||
|
|
||||||
class GroupFactory(Factory):
|
class GroupFactory(Factory):
|
||||||
FACTORY_FOR = Group
|
FACTORY_FOR = Group
|
||||||
|
|
||||||
name = 'test_group'
|
name = 'test_group'
|
||||||
|
|
||||||
|
|
||||||
class CourseEnrollmentAllowedFactory(Factory):
|
class CourseEnrollmentAllowedFactory(Factory):
|
||||||
FACTORY_FOR = CourseEnrollmentAllowed
|
FACTORY_FOR = CourseEnrollmentAllowed
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE)
|
|||||||
TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data')
|
TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data')
|
||||||
TEST_DATA_MODULESTORE['direct']['OPTIONS']['fs_root'] = path('common/test/data')
|
TEST_DATA_MODULESTORE['direct']['OPTIONS']['fs_root'] = path('common/test/data')
|
||||||
|
|
||||||
|
|
||||||
@override_settings(MODULESTORE=TEST_DATA_MODULESTORE)
|
@override_settings(MODULESTORE=TEST_DATA_MODULESTORE)
|
||||||
class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||||
"""
|
"""
|
||||||
@@ -79,7 +80,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
|||||||
import_from_xml(modulestore(), 'common/test/data/', ['full'])
|
import_from_xml(modulestore(), 'common/test/data/', ['full'])
|
||||||
|
|
||||||
ms = modulestore('direct')
|
ms = modulestore('direct')
|
||||||
course = ms.get_item(Location(['i4x','edX','full','course','6.002_Spring_2012', None]))
|
course = ms.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None]))
|
||||||
|
|
||||||
# reverse the ordering
|
# reverse the ordering
|
||||||
reverse_tabs = []
|
reverse_tabs = []
|
||||||
@@ -87,9 +88,9 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
|||||||
if tab['type'] == 'static_tab':
|
if tab['type'] == 'static_tab':
|
||||||
reverse_tabs.insert(0, 'i4x://edX/full/static_tab/{0}'.format(tab['url_slug']))
|
reverse_tabs.insert(0, 'i4x://edX/full/static_tab/{0}'.format(tab['url_slug']))
|
||||||
|
|
||||||
resp = self.client.post(reverse('reorder_static_tabs'), json.dumps({'tabs':reverse_tabs}), "application/json")
|
resp = self.client.post(reverse('reorder_static_tabs'), json.dumps({'tabs': reverse_tabs}), "application/json")
|
||||||
|
|
||||||
course = ms.get_item(Location(['i4x','edX','full','course','6.002_Spring_2012', None]))
|
course = ms.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None]))
|
||||||
|
|
||||||
# compare to make sure that the tabs information is in the expected order after the server call
|
# compare to make sure that the tabs information is in the expected order after the server call
|
||||||
course_tabs = []
|
course_tabs = []
|
||||||
@@ -106,12 +107,12 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
|||||||
'''
|
'''
|
||||||
import_from_xml(modulestore(), 'common/test/data/', ['full'])
|
import_from_xml(modulestore(), 'common/test/data/', ['full'])
|
||||||
ms = modulestore('direct')
|
ms = modulestore('direct')
|
||||||
effort = ms.get_item(Location(['i4x','edX','full','about','effort', None]))
|
effort = ms.get_item(Location(['i4x', 'edX', 'full', 'about', 'effort', None]))
|
||||||
self.assertEqual(effort.definition['data'],'6 hours')
|
self.assertEqual(effort.definition['data'], '6 hours')
|
||||||
|
|
||||||
# this one should be in a non-override folder
|
# this one should be in a non-override folder
|
||||||
effort = ms.get_item(Location(['i4x','edX','full','about','end_date', None]))
|
effort = ms.get_item(Location(['i4x', 'edX', 'full', 'about', 'end_date', None]))
|
||||||
self.assertEqual(effort.definition['data'],'TBD')
|
self.assertEqual(effort.definition['data'], 'TBD')
|
||||||
|
|
||||||
def test_remove_hide_progress_tab(self):
|
def test_remove_hide_progress_tab(self):
|
||||||
import_from_xml(modulestore(), 'common/test/data/', ['full'])
|
import_from_xml(modulestore(), 'common/test/data/', ['full'])
|
||||||
@@ -149,12 +150,12 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
|||||||
|
|
||||||
# now loop through all the units in the course and verify that the clone can render them, which
|
# now loop through all the units in the course and verify that the clone can render them, which
|
||||||
# means the objects are at least present
|
# means the objects are at least present
|
||||||
items = ms.get_items(Location(['i4x','edX', 'full', 'vertical', None]))
|
items = ms.get_items(Location(['i4x', 'edX', 'full', 'vertical', None]))
|
||||||
self.assertGreater(len(items), 0)
|
self.assertGreater(len(items), 0)
|
||||||
clone_items = ms.get_items(Location(['i4x', 'MITx','999','vertical', None]))
|
clone_items = ms.get_items(Location(['i4x', 'MITx', '999', 'vertical', None]))
|
||||||
self.assertGreater(len(clone_items), 0)
|
self.assertGreater(len(clone_items), 0)
|
||||||
for descriptor in items:
|
for descriptor in items:
|
||||||
new_loc = descriptor.location._replace(org = 'MITx', course='999')
|
new_loc = descriptor.location._replace(org='MITx', course='999')
|
||||||
print "Checking {0} should now also be at {1}".format(descriptor.location.url(), new_loc.url())
|
print "Checking {0} should now also be at {1}".format(descriptor.location.url(), new_loc.url())
|
||||||
resp = self.client.get(reverse('edit_unit', kwargs={'location': new_loc.url()}))
|
resp = self.client.get(reverse('edit_unit', kwargs={'location': new_loc.url()}))
|
||||||
self.assertEqual(resp.status_code, 200)
|
self.assertEqual(resp.status_code, 200)
|
||||||
@@ -169,7 +170,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
|||||||
|
|
||||||
delete_course(ms, cs, location)
|
delete_course(ms, cs, location)
|
||||||
|
|
||||||
items = ms.get_items(Location(['i4x','edX', 'full', 'vertical', None]))
|
items = ms.get_items(Location(['i4x', 'edX', 'full', 'vertical', None]))
|
||||||
self.assertEqual(len(items), 0)
|
self.assertEqual(len(items), 0)
|
||||||
|
|
||||||
def verify_content_existence(self, modulestore, root_dir, location, dirname, category_name, filename_suffix=''):
|
def verify_content_existence(self, modulestore, root_dir, location, dirname, category_name, filename_suffix=''):
|
||||||
@@ -213,7 +214,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
|||||||
# reimport
|
# reimport
|
||||||
import_from_xml(ms, root_dir, ['test_export'])
|
import_from_xml(ms, root_dir, ['test_export'])
|
||||||
|
|
||||||
items = ms.get_items(Location(['i4x','edX', 'full', 'vertical', None]))
|
items = ms.get_items(Location(['i4x', 'edX', 'full', 'vertical', None]))
|
||||||
self.assertGreater(len(items), 0)
|
self.assertGreater(len(items), 0)
|
||||||
for descriptor in items:
|
for descriptor in items:
|
||||||
print "Checking {0}....".format(descriptor.location.url())
|
print "Checking {0}....".format(descriptor.location.url())
|
||||||
@@ -229,7 +230,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
|||||||
# import a test course
|
# import a test course
|
||||||
import_from_xml(ms, 'common/test/data/', ['full'])
|
import_from_xml(ms, 'common/test/data/', ['full'])
|
||||||
|
|
||||||
handout_location= Location(['i4x', 'edX', 'full', 'course_info', 'handouts'])
|
handout_location = Location(['i4x', 'edX', 'full', 'course_info', 'handouts'])
|
||||||
|
|
||||||
# get module info
|
# get module info
|
||||||
resp = self.client.get(reverse('module_info', kwargs={'module_location': handout_location}))
|
resp = self.client.get(reverse('module_info', kwargs={'module_location': handout_location}))
|
||||||
@@ -365,8 +366,8 @@ class ContentStoreTest(ModuleStoreTestCase):
|
|||||||
CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
|
CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
|
||||||
|
|
||||||
section_data = {
|
section_data = {
|
||||||
'parent_location' : 'i4x://MITx/999/course/Robot_Super_Course',
|
'parent_location': 'i4x://MITx/999/course/Robot_Super_Course',
|
||||||
'template' : 'i4x://edx/templates/chapter/Empty',
|
'template': 'i4x://edx/templates/chapter/Empty',
|
||||||
'display_name': 'Section One',
|
'display_name': 'Section One',
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,8 +383,8 @@ class ContentStoreTest(ModuleStoreTestCase):
|
|||||||
CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
|
CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
|
||||||
|
|
||||||
problem_data = {
|
problem_data = {
|
||||||
'parent_location' : 'i4x://MITx/999/course/Robot_Super_Course',
|
'parent_location': 'i4x://MITx/999/course/Robot_Super_Course',
|
||||||
'template' : 'i4x://edx/templates/problem/Empty'
|
'template': 'i4x://edx/templates/problem/Empty'
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = self.client.post(reverse('clone_item'), problem_data)
|
resp = self.client.post(reverse('clone_item'), problem_data)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from xmodule.modulestore import Location
|
|||||||
from xmodule.contentstore.content import StaticContent
|
from xmodule.contentstore.content import StaticContent
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
|
|
||||||
class Content:
|
class Content:
|
||||||
def __init__(self, location, content):
|
def __init__(self, location, content):
|
||||||
self.location = location
|
self.location = location
|
||||||
@@ -11,6 +12,7 @@ class Content:
|
|||||||
def get_id(self):
|
def get_id(self):
|
||||||
return StaticContent.get_id_from_location(self.location)
|
return StaticContent.get_id_from_location(self.location)
|
||||||
|
|
||||||
|
|
||||||
class CachingTestCase(TestCase):
|
class CachingTestCase(TestCase):
|
||||||
# Tests for https://edx.lighthouseapp.com/projects/102637/tickets/112-updating-asset-does-not-refresh-the-cached-copy
|
# Tests for https://edx.lighthouseapp.com/projects/102637/tickets/112-updating-asset-does-not-refresh-the-cached-copy
|
||||||
unicodeLocation = Location(u'c4x', u'mitX', u'800', u'thumbnail', u'monsters.jpg')
|
unicodeLocation = Location(u'c4x', u'mitX', u'800', u'thumbnail', u'monsters.jpg')
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class ConvertersTestCase(TestCase):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def struct_to_datetime(struct_time):
|
def struct_to_datetime(struct_time):
|
||||||
return datetime.datetime(struct_time.tm_year, struct_time.tm_mon, struct_time.tm_mday, struct_time.tm_hour,
|
return datetime.datetime(struct_time.tm_year, struct_time.tm_mon, struct_time.tm_mday, struct_time.tm_hour,
|
||||||
struct_time.tm_min, struct_time.tm_sec, tzinfo = UTC())
|
struct_time.tm_min, struct_time.tm_sec, tzinfo=UTC())
|
||||||
|
|
||||||
def compare_dates(self, date1, date2, expected_delta):
|
def compare_dates(self, date1, date2, expected_delta):
|
||||||
dt1 = ConvertersTestCase.struct_to_datetime(date1)
|
dt1 = ConvertersTestCase.struct_to_datetime(date1)
|
||||||
@@ -68,13 +68,14 @@ class CourseTestCase(ModuleStoreTestCase):
|
|||||||
self.client = Client()
|
self.client = Client()
|
||||||
self.client.login(username=uname, password=password)
|
self.client.login(username=uname, password=password)
|
||||||
|
|
||||||
t='i4x://edx/templates/course/Empty'
|
t = 'i4x://edx/templates/course/Empty'
|
||||||
o='MITx'
|
o = 'MITx'
|
||||||
n='999'
|
n = '999'
|
||||||
dn='Robot Super Course'
|
dn = 'Robot Super Course'
|
||||||
self.course_location = Location('i4x', o, n, 'course', 'Robot_Super_Course')
|
self.course_location = Location('i4x', o, n, 'course', 'Robot_Super_Course')
|
||||||
CourseFactory.create(template=t, org=o, number=n, display_name=dn)
|
CourseFactory.create(template=t, org=o, number=n, display_name=dn)
|
||||||
|
|
||||||
|
|
||||||
class CourseDetailsTestCase(CourseTestCase):
|
class CourseDetailsTestCase(CourseTestCase):
|
||||||
def test_virgin_fetch(self):
|
def test_virgin_fetch(self):
|
||||||
details = CourseDetails.fetch(self.course_location)
|
details = CourseDetails.fetch(self.course_location)
|
||||||
@@ -118,6 +119,7 @@ class CourseDetailsTestCase(CourseTestCase):
|
|||||||
self.assertEqual(CourseDetails.update_from_json(jsondetails.__dict__).effort,
|
self.assertEqual(CourseDetails.update_from_json(jsondetails.__dict__).effort,
|
||||||
jsondetails.effort, "After set effort")
|
jsondetails.effort, "After set effort")
|
||||||
|
|
||||||
|
|
||||||
class CourseDetailsViewTest(CourseTestCase):
|
class CourseDetailsViewTest(CourseTestCase):
|
||||||
def alter_field(self, url, details, field, val):
|
def alter_field(self, url, details, field, val):
|
||||||
setattr(details, field, val)
|
setattr(details, field, val)
|
||||||
@@ -141,23 +143,23 @@ class CourseDetailsViewTest(CourseTestCase):
|
|||||||
def test_update_and_fetch(self):
|
def test_update_and_fetch(self):
|
||||||
details = CourseDetails.fetch(self.course_location)
|
details = CourseDetails.fetch(self.course_location)
|
||||||
|
|
||||||
resp = self.client.get(reverse('course_settings', kwargs={'org' : self.course_location.org, 'course' : self.course_location.course,
|
resp = self.client.get(reverse('course_settings', kwargs={'org': self.course_location.org, 'course': self.course_location.course,
|
||||||
'name' : self.course_location.name }))
|
'name': self.course_location.name}))
|
||||||
self.assertContains(resp, '<li><a href="#" class="is-shown" data-section="details">Course Details</a></li>', status_code=200, html=True)
|
self.assertContains(resp, '<li><a href="#" class="is-shown" data-section="details">Course Details</a></li>', status_code=200, html=True)
|
||||||
|
|
||||||
# resp s/b json from here on
|
# resp s/b json from here on
|
||||||
url = reverse('course_settings', kwargs={'org' : self.course_location.org, 'course' : self.course_location.course,
|
url = reverse('course_settings', kwargs={'org': self.course_location.org, 'course': self.course_location.course,
|
||||||
'name' : self.course_location.name, 'section' : 'details' })
|
'name': self.course_location.name, 'section': 'details'})
|
||||||
resp = self.client.get(url)
|
resp = self.client.get(url)
|
||||||
self.compare_details_with_encoding(json.loads(resp.content), details.__dict__, "virgin get")
|
self.compare_details_with_encoding(json.loads(resp.content), details.__dict__, "virgin get")
|
||||||
|
|
||||||
utc = UTC()
|
utc = UTC()
|
||||||
self.alter_field(url, details, 'start_date', datetime.datetime(2012,11,12,1,30, tzinfo=utc))
|
self.alter_field(url, details, 'start_date', datetime.datetime(2012, 11, 12, 1, 30, tzinfo=utc))
|
||||||
self.alter_field(url, details, 'start_date', datetime.datetime(2012,11,1,13,30, tzinfo=utc))
|
self.alter_field(url, details, 'start_date', datetime.datetime(2012, 11, 1, 13, 30, tzinfo=utc))
|
||||||
self.alter_field(url, details, 'end_date', datetime.datetime(2013,2,12,1,30, tzinfo=utc))
|
self.alter_field(url, details, 'end_date', datetime.datetime(2013, 2, 12, 1, 30, tzinfo=utc))
|
||||||
self.alter_field(url, details, 'enrollment_start', datetime.datetime(2012,10,12,1,30, tzinfo=utc))
|
self.alter_field(url, details, 'enrollment_start', datetime.datetime(2012, 10, 12, 1, 30, tzinfo=utc))
|
||||||
|
|
||||||
self.alter_field(url, details, 'enrollment_end', datetime.datetime(2012,11,15,1,30, tzinfo=utc))
|
self.alter_field(url, details, 'enrollment_end', datetime.datetime(2012, 11, 15, 1, 30, tzinfo=utc))
|
||||||
self.alter_field(url, details, 'overview', "Overview")
|
self.alter_field(url, details, 'overview', "Overview")
|
||||||
self.alter_field(url, details, 'intro_video', "intro_video")
|
self.alter_field(url, details, 'intro_video', "intro_video")
|
||||||
self.alter_field(url, details, 'effort', "effort")
|
self.alter_field(url, details, 'effort', "effort")
|
||||||
@@ -190,6 +192,7 @@ class CourseDetailsViewTest(CourseTestCase):
|
|||||||
elif field in encoded and encoded[field] is not None:
|
elif field in encoded and encoded[field] is not None:
|
||||||
self.fail(field + " included in encoding but missing from details at " + context)
|
self.fail(field + " included in encoding but missing from details at " + context)
|
||||||
|
|
||||||
|
|
||||||
class CourseGradingTest(CourseTestCase):
|
class CourseGradingTest(CourseTestCase):
|
||||||
def test_initial_grader(self):
|
def test_initial_grader(self):
|
||||||
descriptor = get_modulestore(self.course_location).get_item(self.course_location)
|
descriptor = get_modulestore(self.course_location).get_item(self.course_location)
|
||||||
|
|||||||
@@ -2,27 +2,28 @@ from cms.djangoapps.contentstore.tests.test_course_settings import CourseTestCas
|
|||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
class CourseUpdateTest(CourseTestCase):
|
class CourseUpdateTest(CourseTestCase):
|
||||||
def test_course_update(self):
|
def test_course_update(self):
|
||||||
# first get the update to force the creation
|
# first get the update to force the creation
|
||||||
url = reverse('course_info', kwargs={'org' : self.course_location.org, 'course' : self.course_location.course,
|
url = reverse('course_info', kwargs={'org': self.course_location.org, 'course': self.course_location.course,
|
||||||
'name' : self.course_location.name })
|
'name': self.course_location.name})
|
||||||
self.client.get(url)
|
self.client.get(url)
|
||||||
|
|
||||||
content = '<iframe width="560" height="315" src="http://www.youtube.com/embed/RocY-Jd93XU" frameborder="0"></iframe>'
|
content = '<iframe width="560" height="315" src="http://www.youtube.com/embed/RocY-Jd93XU" frameborder="0"></iframe>'
|
||||||
payload = { 'content' : content,
|
payload = {'content': content,
|
||||||
'date' : 'January 8, 2013'}
|
'date': 'January 8, 2013'}
|
||||||
url = reverse('course_info', kwargs={'org' : self.course_location.org, 'course' : self.course_location.course,
|
url = reverse('course_info', kwargs={'org': self.course_location.org, 'course': self.course_location.course,
|
||||||
'provided_id' : ''})
|
'provided_id': ''})
|
||||||
|
|
||||||
resp = self.client.post(url, json.dumps(payload), "application/json")
|
resp = self.client.post(url, json.dumps(payload), "application/json")
|
||||||
|
|
||||||
payload= json.loads(resp.content)
|
payload = json.loads(resp.content)
|
||||||
|
|
||||||
self.assertHTMLEqual(content, payload['content'], "single iframe")
|
self.assertHTMLEqual(content, payload['content'], "single iframe")
|
||||||
|
|
||||||
url = reverse('course_info', kwargs={'org' : self.course_location.org, 'course' : self.course_location.course,
|
url = reverse('course_info', kwargs={'org': self.course_location.org, 'course': self.course_location.course,
|
||||||
'provided_id' : payload['id']})
|
'provided_id': payload['id']})
|
||||||
content += '<div>div <p>p</p></div>'
|
content += '<div>div <p>p</p></div>'
|
||||||
payload['content'] = content
|
payload['content'] = content
|
||||||
resp = self.client.post(url, json.dumps(payload), "application/json")
|
resp = self.client.post(url, json.dumps(payload), "application/json")
|
||||||
|
|||||||
@@ -2,15 +2,16 @@ from cms.djangoapps.contentstore import utils
|
|||||||
import mock
|
import mock
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
|
|
||||||
class LMSLinksTestCase(TestCase):
|
class LMSLinksTestCase(TestCase):
|
||||||
def about_page_test(self):
|
def about_page_test(self):
|
||||||
location = 'i4x','mitX','101','course', 'test'
|
location = 'i4x', 'mitX', '101', 'course', 'test'
|
||||||
utils.get_course_id = mock.Mock(return_value="mitX/101/test")
|
utils.get_course_id = mock.Mock(return_value="mitX/101/test")
|
||||||
link = utils.get_lms_link_for_about_page(location)
|
link = utils.get_lms_link_for_about_page(location)
|
||||||
self.assertEquals(link, "//localhost:8000/courses/mitX/101/test/about")
|
self.assertEquals(link, "//localhost:8000/courses/mitX/101/test/about")
|
||||||
|
|
||||||
def ls_link_test(self):
|
def ls_link_test(self):
|
||||||
location = 'i4x','mitX','101','vertical', 'contacting_us'
|
location = 'i4x', 'mitX', '101', 'vertical', 'contacting_us'
|
||||||
utils.get_course_id = mock.Mock(return_value="mitX/101/test")
|
utils.get_course_id = mock.Mock(return_value="mitX/101/test")
|
||||||
link = utils.get_lms_link_for_item(location, False)
|
link = utils.get_lms_link_for_item(location, False)
|
||||||
self.assertEquals(link, "//localhost:8000/courses/mitX/101/test/jump_to/i4x://mitX/101/vertical/contacting_us")
|
self.assertEquals(link, "//localhost:8000/courses/mitX/101/test/jump_to/i4x://mitX/101/vertical/contacting_us")
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ from xmodule.seq_module import SequenceDescriptor
|
|||||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||||
from utils import ModuleStoreTestCase, parse_json, user, registration
|
from utils import ModuleStoreTestCase, parse_json, user, registration
|
||||||
|
|
||||||
|
|
||||||
class ContentStoreTestCase(ModuleStoreTestCase):
|
class ContentStoreTestCase(ModuleStoreTestCase):
|
||||||
def _login(self, email, pw):
|
def _login(self, email, pw):
|
||||||
"""Login. View should always return 200. The success/fail is in the
|
"""Login. View should always return 200. The success/fail is in the
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from django.contrib.auth.models import User
|
|||||||
import xmodule.modulestore.django
|
import xmodule.modulestore.django
|
||||||
from xmodule.templates import update_templates
|
from xmodule.templates import update_templates
|
||||||
|
|
||||||
|
|
||||||
class ModuleStoreTestCase(TestCase):
|
class ModuleStoreTestCase(TestCase):
|
||||||
""" Subclass for any test case that uses the mongodb
|
""" Subclass for any test case that uses the mongodb
|
||||||
module store. This populates a uniquely named modulestore
|
module store. This populates a uniquely named modulestore
|
||||||
@@ -22,7 +23,7 @@ class ModuleStoreTestCase(TestCase):
|
|||||||
|
|
||||||
# Use the current seconds since epoch to differentiate
|
# Use the current seconds since epoch to differentiate
|
||||||
# the mongo collections on jenkins.
|
# the mongo collections on jenkins.
|
||||||
sec_since_epoch = '%s' % int(time()*100)
|
sec_since_epoch = '%s' % int(time() * 100)
|
||||||
self.orig_MODULESTORE = copy.deepcopy(settings.MODULESTORE)
|
self.orig_MODULESTORE = copy.deepcopy(settings.MODULESTORE)
|
||||||
self.test_MODULESTORE = self.orig_MODULESTORE
|
self.test_MODULESTORE = self.orig_MODULESTORE
|
||||||
self.test_MODULESTORE['default']['OPTIONS']['collection'] = 'modulestore_%s' % sec_since_epoch
|
self.test_MODULESTORE['default']['OPTIONS']['collection'] = 'modulestore_%s' % sec_since_epoch
|
||||||
@@ -50,14 +51,17 @@ class ModuleStoreTestCase(TestCase):
|
|||||||
|
|
||||||
super(ModuleStoreTestCase, self)._post_teardown()
|
super(ModuleStoreTestCase, self)._post_teardown()
|
||||||
|
|
||||||
|
|
||||||
def parse_json(response):
|
def parse_json(response):
|
||||||
"""Parse response, which is assumed to be json"""
|
"""Parse response, which is assumed to be json"""
|
||||||
return json.loads(response.content)
|
return json.loads(response.content)
|
||||||
|
|
||||||
|
|
||||||
def user(email):
|
def user(email):
|
||||||
"""look up a user by email"""
|
"""look up a user by email"""
|
||||||
return User.objects.get(email=email)
|
return User.objects.get(email=email)
|
||||||
|
|
||||||
|
|
||||||
def registration(email):
|
def registration(email):
|
||||||
"""look up registration object by email"""
|
"""look up registration object by email"""
|
||||||
return Registration.objects.get(user__email=email)
|
return Registration.objects.get(user__email=email)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
|
|||||||
|
|
||||||
DIRECT_ONLY_CATEGORIES = ['course', 'chapter', 'sequential', 'about', 'static_tab', 'course_info']
|
DIRECT_ONLY_CATEGORIES = ['course', 'chapter', 'sequential', 'about', 'static_tab', 'course_info']
|
||||||
|
|
||||||
|
|
||||||
def get_modulestore(location):
|
def get_modulestore(location):
|
||||||
"""
|
"""
|
||||||
Returns the correct modulestore to use for modifying the specified location
|
Returns the correct modulestore to use for modifying the specified location
|
||||||
@@ -17,6 +18,7 @@ def get_modulestore(location):
|
|||||||
else:
|
else:
|
||||||
return modulestore()
|
return modulestore()
|
||||||
|
|
||||||
|
|
||||||
def get_course_location_for_item(location):
|
def get_course_location_for_item(location):
|
||||||
'''
|
'''
|
||||||
cdodge: for a given Xmodule, return the course that it belongs to
|
cdodge: for a given Xmodule, return the course that it belongs to
|
||||||
@@ -46,6 +48,7 @@ def get_course_location_for_item(location):
|
|||||||
|
|
||||||
return location
|
return location
|
||||||
|
|
||||||
|
|
||||||
def get_course_for_item(location):
|
def get_course_for_item(location):
|
||||||
'''
|
'''
|
||||||
cdodge: for a given Xmodule, return the course that it belongs to
|
cdodge: for a given Xmodule, return the course that it belongs to
|
||||||
@@ -85,6 +88,7 @@ def get_lms_link_for_item(location, preview=False):
|
|||||||
|
|
||||||
return lms_link
|
return lms_link
|
||||||
|
|
||||||
|
|
||||||
def get_lms_link_for_about_page(location):
|
def get_lms_link_for_about_page(location):
|
||||||
"""
|
"""
|
||||||
Returns the url to the course about page from the location tuple.
|
Returns the url to the course about page from the location tuple.
|
||||||
@@ -99,6 +103,7 @@ def get_lms_link_for_about_page(location):
|
|||||||
|
|
||||||
return lms_link
|
return lms_link
|
||||||
|
|
||||||
|
|
||||||
def get_course_id(location):
|
def get_course_id(location):
|
||||||
"""
|
"""
|
||||||
Returns the course_id from a given the location tuple.
|
Returns the course_id from a given the location tuple.
|
||||||
@@ -106,6 +111,7 @@ def get_course_id(location):
|
|||||||
# TODO: These will need to be changed to point to the particular instance of this problem in the particular course
|
# TODO: These will need to be changed to point to the particular instance of this problem in the particular course
|
||||||
return modulestore().get_containing_courses(Location(location))[0].id
|
return modulestore().get_containing_courses(Location(location))[0].id
|
||||||
|
|
||||||
|
|
||||||
class UnitState(object):
|
class UnitState(object):
|
||||||
draft = 'draft'
|
draft = 'draft'
|
||||||
private = 'private'
|
private = 'private'
|
||||||
@@ -135,6 +141,7 @@ def compute_unit_state(unit):
|
|||||||
def get_date_display(date):
|
def get_date_display(date):
|
||||||
return date.strftime("%d %B, %Y at %I:%M %p")
|
return date.strftime("%d %B, %Y at %I:%M %p")
|
||||||
|
|
||||||
|
|
||||||
def update_item(location, value):
|
def update_item(location, value):
|
||||||
"""
|
"""
|
||||||
If value is None, delete the db entry. Otherwise, update it using the correct modulestore.
|
If value is None, delete the db entry. Otherwise, update it using the correct modulestore.
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ def signup(request):
|
|||||||
csrf_token = csrf(request)['csrf_token']
|
csrf_token = csrf(request)['csrf_token']
|
||||||
return render_to_response('signup.html', {'csrf': csrf_token})
|
return render_to_response('signup.html', {'csrf': csrf_token})
|
||||||
|
|
||||||
|
|
||||||
@ssl_login_shortcut
|
@ssl_login_shortcut
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
def login_page(request):
|
def login_page(request):
|
||||||
@@ -114,7 +115,7 @@ def index(request):
|
|||||||
courses = filter(course_filter, courses)
|
courses = filter(course_filter, courses)
|
||||||
|
|
||||||
return render_to_response('index.html', {
|
return render_to_response('index.html', {
|
||||||
'new_course_template' : Location('i4x', 'edx', 'templates', 'course', 'Empty'),
|
'new_course_template': Location('i4x', 'edx', 'templates', 'course', 'Empty'),
|
||||||
'courses': [(course.metadata.get('display_name'),
|
'courses': [(course.metadata.get('display_name'),
|
||||||
reverse('course_index', args=[
|
reverse('course_index', args=[
|
||||||
course.location.org,
|
course.location.org,
|
||||||
@@ -159,10 +160,10 @@ def course_index(request, org, course, name):
|
|||||||
if not has_access(request.user, location):
|
if not has_access(request.user, location):
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
|
|
||||||
upload_asset_callback_url = reverse('upload_asset', kwargs = {
|
upload_asset_callback_url = reverse('upload_asset', kwargs={
|
||||||
'org' : org,
|
'org': org,
|
||||||
'course' : course,
|
'course': course,
|
||||||
'coursename' : name
|
'coursename': name
|
||||||
})
|
})
|
||||||
|
|
||||||
course = modulestore().get_item(location)
|
course = modulestore().get_item(location)
|
||||||
@@ -213,7 +214,7 @@ def edit_subsection(request, location):
|
|||||||
|
|
||||||
# remove all metadata from the generic dictionary that is presented in a more normalized UI
|
# remove all metadata from the generic dictionary that is presented in a more normalized UI
|
||||||
|
|
||||||
policy_metadata = dict((key,value) for key, value in item.metadata.iteritems()
|
policy_metadata = dict((key, value) for key, value in item.metadata.iteritems()
|
||||||
if key not in ['display_name', 'start', 'due', 'format'] and key not in item.system_metadata_fields)
|
if key not in ['display_name', 'start', 'due', 'format'] and key not in item.system_metadata_fields)
|
||||||
|
|
||||||
can_view_live = False
|
can_view_live = False
|
||||||
@@ -233,9 +234,9 @@ def edit_subsection(request, location):
|
|||||||
'course_graders': json.dumps(CourseGradingModel.fetch(course.location).graders),
|
'course_graders': json.dumps(CourseGradingModel.fetch(course.location).graders),
|
||||||
'parent_location': course.location,
|
'parent_location': course.location,
|
||||||
'parent_item': parent,
|
'parent_item': parent,
|
||||||
'policy_metadata' : policy_metadata,
|
'policy_metadata': policy_metadata,
|
||||||
'subsection_units' : subsection_units,
|
'subsection_units': subsection_units,
|
||||||
'can_view_live' : can_view_live
|
'can_view_live': can_view_live
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -294,7 +295,7 @@ def edit_unit(request, location):
|
|||||||
# so let's generate the link url here
|
# so let's generate the link url here
|
||||||
|
|
||||||
# need to figure out where this item is in the list of children as the preview will need this
|
# need to figure out where this item is in the list of children as the preview will need this
|
||||||
index =1
|
index = 1
|
||||||
for child in containing_subsection.get_children():
|
for child in containing_subsection.get_children():
|
||||||
if child.location == item.location:
|
if child.location == item.location:
|
||||||
break
|
break
|
||||||
@@ -348,6 +349,7 @@ def preview_component(request, location):
|
|||||||
'editor': wrap_xmodule(component.get_html, component, 'xmodule_edit.html')(),
|
'editor': wrap_xmodule(component.get_html, component, 'xmodule_edit.html')(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@expect_json
|
@expect_json
|
||||||
@login_required
|
@login_required
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
@@ -527,7 +529,7 @@ def load_preview_module(request, preview_id, descriptor, instance_state, shared_
|
|||||||
module.get_html = replace_static_urls(
|
module.get_html = replace_static_urls(
|
||||||
module.get_html,
|
module.get_html,
|
||||||
module.metadata.get('data_dir', module.location.course),
|
module.metadata.get('data_dir', module.location.course),
|
||||||
course_namespace = Location([module.location.tag, module.location.org, module.location.course, None, None])
|
course_namespace=Location([module.location.tag, module.location.org, module.location.course, None, None])
|
||||||
)
|
)
|
||||||
save_preview_state(request, preview_id, descriptor.location.url(),
|
save_preview_state(request, preview_id, descriptor.location.url(),
|
||||||
module.get_instance_state(), module.get_shared_state())
|
module.get_instance_state(), module.get_shared_state())
|
||||||
@@ -588,7 +590,7 @@ def delete_item(request):
|
|||||||
# semantics of delete_item whereby the store is draft aware. Right now calling
|
# semantics of delete_item whereby the store is draft aware. Right now calling
|
||||||
# delete_item on a vertical tries to delete the draft version leaving the
|
# delete_item on a vertical tries to delete the draft version leaving the
|
||||||
# requested delete to never occur
|
# requested delete to never occur
|
||||||
if item.location.revision is None and item.location.category=='vertical' and delete_all_versions:
|
if item.location.revision is None and item.location.category == 'vertical' and delete_all_versions:
|
||||||
modulestore('direct').delete_item(item.location)
|
modulestore('direct').delete_item(item.location)
|
||||||
|
|
||||||
return HttpResponse()
|
return HttpResponse()
|
||||||
@@ -664,6 +666,7 @@ def create_draft(request):
|
|||||||
|
|
||||||
return HttpResponse()
|
return HttpResponse()
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@expect_json
|
@expect_json
|
||||||
def publish_draft(request):
|
def publish_draft(request):
|
||||||
@@ -693,6 +696,7 @@ def unpublish_unit(request):
|
|||||||
|
|
||||||
return HttpResponse()
|
return HttpResponse()
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@expect_json
|
@expect_json
|
||||||
def clone_item(request):
|
def clone_item(request):
|
||||||
@@ -725,6 +729,8 @@ def clone_item(request):
|
|||||||
|
|
||||||
#@login_required
|
#@login_required
|
||||||
#@ensure_csrf_cookie
|
#@ensure_csrf_cookie
|
||||||
|
|
||||||
|
|
||||||
def upload_asset(request, org, course, coursename):
|
def upload_asset(request, org, course, coursename):
|
||||||
'''
|
'''
|
||||||
cdodge: this method allows for POST uploading of files into the course asset library, which will
|
cdodge: this method allows for POST uploading of files into the course asset library, which will
|
||||||
@@ -775,11 +781,11 @@ def upload_asset(request, org, course, coursename):
|
|||||||
# readback the saved content - we need the database timestamp
|
# readback the saved content - we need the database timestamp
|
||||||
readback = contentstore().find(content.location)
|
readback = contentstore().find(content.location)
|
||||||
|
|
||||||
response_payload = {'displayname' : content.name,
|
response_payload = {'displayname': content.name,
|
||||||
'uploadDate' : get_date_display(readback.last_modified_at),
|
'uploadDate': get_date_display(readback.last_modified_at),
|
||||||
'url' : StaticContent.get_url_path_from_location(content.location),
|
'url': StaticContent.get_url_path_from_location(content.location),
|
||||||
'thumb_url' : StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_content is not None else None,
|
'thumb_url': StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_content is not None else None,
|
||||||
'msg' : 'Upload completed'
|
'msg': 'Upload completed'
|
||||||
}
|
}
|
||||||
|
|
||||||
response = HttpResponse(json.dumps(response_payload))
|
response = HttpResponse(json.dumps(response_payload))
|
||||||
@@ -789,6 +795,8 @@ def upload_asset(request, org, course, coursename):
|
|||||||
'''
|
'''
|
||||||
This view will return all CMS users who are editors for the specified course
|
This view will return all CMS users who are editors for the specified course
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
def manage_users(request, location):
|
def manage_users(request, location):
|
||||||
@@ -803,16 +811,16 @@ def manage_users(request, location):
|
|||||||
'active_tab': 'users',
|
'active_tab': 'users',
|
||||||
'context_course': course_module,
|
'context_course': course_module,
|
||||||
'staff': get_users_in_course_group_by_role(location, STAFF_ROLE_NAME),
|
'staff': get_users_in_course_group_by_role(location, STAFF_ROLE_NAME),
|
||||||
'add_user_postback_url' : reverse('add_user', args=[location]).rstrip('/'),
|
'add_user_postback_url': reverse('add_user', args=[location]).rstrip('/'),
|
||||||
'remove_user_postback_url' : reverse('remove_user', args=[location]).rstrip('/'),
|
'remove_user_postback_url': reverse('remove_user', args=[location]).rstrip('/'),
|
||||||
'allow_actions' : has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME),
|
'allow_actions': has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME),
|
||||||
'request_user_id' : request.user.id
|
'request_user_id': request.user.id
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
def create_json_response(errmsg = None):
|
def create_json_response(errmsg=None):
|
||||||
if errmsg is not None:
|
if errmsg is not None:
|
||||||
resp = HttpResponse(json.dumps({'Status': 'Failed', 'ErrMsg' : errmsg}))
|
resp = HttpResponse(json.dumps({'Status': 'Failed', 'ErrMsg': errmsg}))
|
||||||
else:
|
else:
|
||||||
resp = HttpResponse(json.dumps({'Status': 'OK'}))
|
resp = HttpResponse(json.dumps({'Status': 'OK'}))
|
||||||
|
|
||||||
@@ -822,13 +830,15 @@ def create_json_response(errmsg = None):
|
|||||||
This POST-back view will add a user - specified by email - to the list of editors for
|
This POST-back view will add a user - specified by email - to the list of editors for
|
||||||
the specified course
|
the specified course
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
@expect_json
|
@expect_json
|
||||||
@login_required
|
@login_required
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
def add_user(request, location):
|
def add_user(request, location):
|
||||||
email = request.POST["email"]
|
email = request.POST["email"]
|
||||||
|
|
||||||
if email=='':
|
if email == '':
|
||||||
return create_json_response('Please specify an email address.')
|
return create_json_response('Please specify an email address.')
|
||||||
|
|
||||||
# check that logged in user has admin permissions to this course
|
# check that logged in user has admin permissions to this course
|
||||||
@@ -854,6 +864,8 @@ def add_user(request, location):
|
|||||||
This POST-back view will remove a user - specified by email - from the list of editors for
|
This POST-back view will remove a user - specified by email - from the list of editors for
|
||||||
the specified course
|
the specified course
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
@expect_json
|
@expect_json
|
||||||
@login_required
|
@login_required
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
@@ -881,6 +893,7 @@ def remove_user(request, location):
|
|||||||
def landing(request, org, course, coursename):
|
def landing(request, org, course, coursename):
|
||||||
return render_to_response('temp-course-landing.html', {})
|
return render_to_response('temp-course-landing.html', {})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
def static_pages(request, org, course, coursename):
|
def static_pages(request, org, course, coursename):
|
||||||
@@ -921,7 +934,7 @@ def reorder_static_tabs(request):
|
|||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
# load all reference tabs, return BadRequest if we can't find any of them
|
# load all reference tabs, return BadRequest if we can't find any of them
|
||||||
tab_items =[]
|
tab_items = []
|
||||||
for tab in tabs:
|
for tab in tabs:
|
||||||
item = modulestore('direct').get_item(Location(tab))
|
item = modulestore('direct').get_item(Location(tab))
|
||||||
if item is None:
|
if item is None:
|
||||||
@@ -935,8 +948,8 @@ def reorder_static_tabs(request):
|
|||||||
for tab in course.tabs:
|
for tab in course.tabs:
|
||||||
if tab['type'] == 'static_tab':
|
if tab['type'] == 'static_tab':
|
||||||
reordered_tabs.append({'type': 'static_tab',
|
reordered_tabs.append({'type': 'static_tab',
|
||||||
'name' : tab_items[static_tab_idx].metadata.get('display_name'),
|
'name': tab_items[static_tab_idx].metadata.get('display_name'),
|
||||||
'url_slug' : tab_items[static_tab_idx].location.name})
|
'url_slug': tab_items[static_tab_idx].location.name})
|
||||||
static_tab_idx += 1
|
static_tab_idx += 1
|
||||||
else:
|
else:
|
||||||
reordered_tabs.append(tab)
|
reordered_tabs.append(tab)
|
||||||
@@ -980,10 +993,11 @@ def edit_tabs(request, org, course, coursename):
|
|||||||
|
|
||||||
return render_to_response('edit-tabs.html', {
|
return render_to_response('edit-tabs.html', {
|
||||||
'active_tab': 'pages',
|
'active_tab': 'pages',
|
||||||
'context_course':course_item,
|
'context_course': course_item,
|
||||||
'components': components
|
'components': components
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
def not_found(request):
|
def not_found(request):
|
||||||
return render_to_response('error.html', {'error': '404'})
|
return render_to_response('error.html', {'error': '404'})
|
||||||
|
|
||||||
@@ -1014,11 +1028,12 @@ def course_info(request, org, course, name, provided_id=None):
|
|||||||
return render_to_response('course_info.html', {
|
return render_to_response('course_info.html', {
|
||||||
'active_tab': 'courseinfo-tab',
|
'active_tab': 'courseinfo-tab',
|
||||||
'context_course': course_module,
|
'context_course': course_module,
|
||||||
'url_base' : "/" + org + "/" + course + "/",
|
'url_base': "/" + org + "/" + course + "/",
|
||||||
'course_updates' : json.dumps(get_course_updates(location)),
|
'course_updates': json.dumps(get_course_updates(location)),
|
||||||
'handouts_location': Location(['i4x', org, course, 'course_info', 'handouts']).url()
|
'handouts_location': Location(['i4x', org, course, 'course_info', 'handouts']).url()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@expect_json
|
@expect_json
|
||||||
@login_required
|
@login_required
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
@@ -1075,8 +1090,8 @@ def module_info(request, module_location):
|
|||||||
else:
|
else:
|
||||||
real_method = request.method
|
real_method = request.method
|
||||||
|
|
||||||
rewrite_static_links = request.GET.get('rewrite_url_links','True') in ['True', 'true']
|
rewrite_static_links = request.GET.get('rewrite_url_links', 'True') in ['True', 'true']
|
||||||
logging.debug('rewrite_static_links = {0} {1}'.format(request.GET.get('rewrite_url_links','False'), rewrite_static_links))
|
logging.debug('rewrite_static_links = {0} {1}'.format(request.GET.get('rewrite_url_links', 'False'), rewrite_static_links))
|
||||||
|
|
||||||
# check that logged in user has permissions to this item
|
# check that logged in user has permissions to this item
|
||||||
if not has_access(request.user, location):
|
if not has_access(request.user, location):
|
||||||
@@ -1089,6 +1104,7 @@ def module_info(request, module_location):
|
|||||||
else:
|
else:
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
def get_course_settings(request, org, course, name):
|
def get_course_settings(request, org, course, name):
|
||||||
@@ -1109,9 +1125,10 @@ def get_course_settings(request, org, course, name):
|
|||||||
return render_to_response('settings.html', {
|
return render_to_response('settings.html', {
|
||||||
'active_tab': 'settings',
|
'active_tab': 'settings',
|
||||||
'context_course': course_module,
|
'context_course': course_module,
|
||||||
'course_details' : json.dumps(course_details, cls=CourseSettingsEncoder)
|
'course_details': json.dumps(course_details, cls=CourseSettingsEncoder)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@expect_json
|
@expect_json
|
||||||
@login_required
|
@login_required
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
@@ -1137,12 +1154,13 @@ def course_settings_updates(request, org, course, name, section):
|
|||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
# Cannot just do a get w/o knowing the course name :-(
|
# Cannot just do a get w/o knowing the course name :-(
|
||||||
return HttpResponse(json.dumps(manager.fetch(Location(['i4x', org, course, 'course',name])), cls=CourseSettingsEncoder),
|
return HttpResponse(json.dumps(manager.fetch(Location(['i4x', org, course, 'course', name])), cls=CourseSettingsEncoder),
|
||||||
mimetype="application/json")
|
mimetype="application/json")
|
||||||
elif request.method == 'POST': # post or put, doesn't matter.
|
elif request.method == 'POST': # post or put, doesn't matter.
|
||||||
return HttpResponse(json.dumps(manager.update_from_json(request.POST), cls=CourseSettingsEncoder),
|
return HttpResponse(json.dumps(manager.update_from_json(request.POST), cls=CourseSettingsEncoder),
|
||||||
mimetype="application/json")
|
mimetype="application/json")
|
||||||
|
|
||||||
|
|
||||||
@expect_json
|
@expect_json
|
||||||
@login_required
|
@login_required
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
@@ -1167,14 +1185,14 @@ def course_grader_updates(request, org, course, name, grader_index=None):
|
|||||||
|
|
||||||
if real_method == 'GET':
|
if real_method == 'GET':
|
||||||
# Cannot just do a get w/o knowing the course name :-(
|
# Cannot just do a get w/o knowing the course name :-(
|
||||||
return HttpResponse(json.dumps(CourseGradingModel.fetch_grader(Location(['i4x', org, course, 'course',name]), grader_index)),
|
return HttpResponse(json.dumps(CourseGradingModel.fetch_grader(Location(['i4x', org, course, 'course', name]), grader_index)),
|
||||||
mimetype="application/json")
|
mimetype="application/json")
|
||||||
elif real_method == "DELETE":
|
elif real_method == "DELETE":
|
||||||
# ??? Shoudl this return anything? Perhaps success fail?
|
# ??? Shoudl this return anything? Perhaps success fail?
|
||||||
CourseGradingModel.delete_grader(Location(['i4x', org, course, 'course',name]), grader_index)
|
CourseGradingModel.delete_grader(Location(['i4x', org, course, 'course', name]), grader_index)
|
||||||
return HttpResponse()
|
return HttpResponse()
|
||||||
elif request.method == 'POST': # post or put, doesn't matter.
|
elif request.method == 'POST': # post or put, doesn't matter.
|
||||||
return HttpResponse(json.dumps(CourseGradingModel.update_grader_from_json(Location(['i4x', org, course, 'course',name]), request.POST)),
|
return HttpResponse(json.dumps(CourseGradingModel.update_grader_from_json(Location(['i4x', org, course, 'course', name]), request.POST)),
|
||||||
mimetype="application/json")
|
mimetype="application/json")
|
||||||
|
|
||||||
|
|
||||||
@@ -1193,10 +1211,10 @@ def asset_index(request, org, course, name):
|
|||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
|
||||||
upload_asset_callback_url = reverse('upload_asset', kwargs = {
|
upload_asset_callback_url = reverse('upload_asset', kwargs={
|
||||||
'org' : org,
|
'org': org,
|
||||||
'course' : course,
|
'course': course,
|
||||||
'coursename' : name
|
'coursename': name
|
||||||
})
|
})
|
||||||
|
|
||||||
course_module = modulestore().get_item(location)
|
course_module = modulestore().get_item(location)
|
||||||
@@ -1237,6 +1255,7 @@ def asset_index(request, org, course, name):
|
|||||||
def edge(request):
|
def edge(request):
|
||||||
return render_to_response('university_profiles/edge.html', {})
|
return render_to_response('university_profiles/edge.html', {})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@expect_json
|
@expect_json
|
||||||
def create_new_course(request):
|
def create_new_course(request):
|
||||||
@@ -1288,6 +1307,7 @@ def create_new_course(request):
|
|||||||
|
|
||||||
return HttpResponse(json.dumps({'id': new_course.location.url()}))
|
return HttpResponse(json.dumps({'id': new_course.location.url()}))
|
||||||
|
|
||||||
|
|
||||||
def initialize_course_tabs(course):
|
def initialize_course_tabs(course):
|
||||||
# set up the default tabs
|
# set up the default tabs
|
||||||
# I've added this because when we add static tabs, the LMS either expects a None for the tabs list or
|
# I've added this because when we add static tabs, the LMS either expects a None for the tabs list or
|
||||||
@@ -1305,6 +1325,7 @@ def initialize_course_tabs(course):
|
|||||||
|
|
||||||
modulestore('direct').update_metadata(course.location.url(), course.own_metadata)
|
modulestore('direct').update_metadata(course.location.url(), course.own_metadata)
|
||||||
|
|
||||||
|
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
@login_required
|
@login_required
|
||||||
def import_course(request, org, course, name):
|
def import_course(request, org, course, name):
|
||||||
@@ -1343,7 +1364,7 @@ def import_course(request, org, course, name):
|
|||||||
|
|
||||||
# find the 'course.xml' file
|
# find the 'course.xml' file
|
||||||
|
|
||||||
for r,d,f in os.walk(course_dir):
|
for r, d, f in os.walk(course_dir):
|
||||||
for files in f:
|
for files in f:
|
||||||
if files == 'course.xml':
|
if files == 'course.xml':
|
||||||
break
|
break
|
||||||
@@ -1357,10 +1378,10 @@ def import_course(request, org, course, name):
|
|||||||
|
|
||||||
if r != course_dir:
|
if r != course_dir:
|
||||||
for fname in os.listdir(r):
|
for fname in os.listdir(r):
|
||||||
shutil.move(r/fname, course_dir)
|
shutil.move(r / fname, course_dir)
|
||||||
|
|
||||||
module_store, course_items = import_from_xml(modulestore('direct'), settings.GITHUB_REPO_ROOT,
|
module_store, course_items = import_from_xml(modulestore('direct'), settings.GITHUB_REPO_ROOT,
|
||||||
[course_subdir], load_error_modules=False, static_content_store=contentstore(), target_location_namespace = Location(location))
|
[course_subdir], load_error_modules=False, static_content_store=contentstore(), target_location_namespace=Location(location))
|
||||||
|
|
||||||
# we can blow this away when we're done importing.
|
# we can blow this away when we're done importing.
|
||||||
shutil.rmtree(course_dir)
|
shutil.rmtree(course_dir)
|
||||||
@@ -1376,12 +1397,13 @@ def import_course(request, org, course, name):
|
|||||||
return render_to_response('import.html', {
|
return render_to_response('import.html', {
|
||||||
'context_course': course_module,
|
'context_course': course_module,
|
||||||
'active_tab': 'import',
|
'active_tab': 'import',
|
||||||
'successful_import_redirect_url' : reverse('course_index', args=[
|
'successful_import_redirect_url': reverse('course_index', args=[
|
||||||
course_module.location.org,
|
course_module.location.org,
|
||||||
course_module.location.course,
|
course_module.location.course,
|
||||||
course_module.location.name])
|
course_module.location.name])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
@login_required
|
@login_required
|
||||||
def generate_export_course(request, org, course, name):
|
def generate_export_course(request, org, course, name):
|
||||||
@@ -1391,7 +1413,7 @@ def generate_export_course(request, org, course, name):
|
|||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
|
|
||||||
loc = Location(location)
|
loc = Location(location)
|
||||||
export_file = NamedTemporaryFile(prefix=name+'.', suffix=".tar.gz")
|
export_file = NamedTemporaryFile(prefix=name + '.', suffix=".tar.gz")
|
||||||
|
|
||||||
root_dir = path(mkdtemp())
|
root_dir = path(mkdtemp())
|
||||||
|
|
||||||
@@ -1404,11 +1426,11 @@ def generate_export_course(request, org, course, name):
|
|||||||
|
|
||||||
logging.debug('tar file being generated at {0}'.format(export_file.name))
|
logging.debug('tar file being generated at {0}'.format(export_file.name))
|
||||||
tf = tarfile.open(name=export_file.name, mode='w:gz')
|
tf = tarfile.open(name=export_file.name, mode='w:gz')
|
||||||
tf.add(root_dir/name, arcname=name)
|
tf.add(root_dir / name, arcname=name)
|
||||||
tf.close()
|
tf.close()
|
||||||
|
|
||||||
# remove temp dir
|
# remove temp dir
|
||||||
shutil.rmtree(root_dir/name)
|
shutil.rmtree(root_dir / name)
|
||||||
|
|
||||||
wrapper = FileWrapper(export_file)
|
wrapper = FileWrapper(export_file)
|
||||||
response = HttpResponse(wrapper, content_type='application/x-tgz')
|
response = HttpResponse(wrapper, content_type='application/x-tgz')
|
||||||
@@ -1430,9 +1452,10 @@ def export_course(request, org, course, name):
|
|||||||
return render_to_response('export.html', {
|
return render_to_response('export.html', {
|
||||||
'context_course': course_module,
|
'context_course': course_module,
|
||||||
'active_tab': 'export',
|
'active_tab': 'export',
|
||||||
'successful_import_redirect_url' : ''
|
'successful_import_redirect_url': ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
def event(request):
|
def event(request):
|
||||||
'''
|
'''
|
||||||
A noop to swallow the analytics call so that cms methods don't spook and poor developers looking at
|
A noop to swallow the analytics call so that cms methods don't spook and poor developers looking at
|
||||||
|
|||||||
@@ -47,12 +47,12 @@ class CourseGradingModel(object):
|
|||||||
# return empty model
|
# return empty model
|
||||||
else:
|
else:
|
||||||
return {
|
return {
|
||||||
"id" : index,
|
"id": index,
|
||||||
"type" : "",
|
"type": "",
|
||||||
"min_count" : 0,
|
"min_count": 0,
|
||||||
"drop_count" : 0,
|
"drop_count": 0,
|
||||||
"short_label" : None,
|
"short_label": None,
|
||||||
"weight" : 0
|
"weight": 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -75,7 +75,7 @@ class CourseGradingModel(object):
|
|||||||
course_location = Location(course_location)
|
course_location = Location(course_location)
|
||||||
|
|
||||||
descriptor = get_modulestore(course_location).get_item(course_location)
|
descriptor = get_modulestore(course_location).get_item(course_location)
|
||||||
return {'grace_period' : CourseGradingModel.convert_set_grace_period(descriptor) }
|
return {'grace_period': CourseGradingModel.convert_set_grace_period(descriptor)}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_from_json(jsondict):
|
def update_from_json(jsondict):
|
||||||
@@ -211,9 +211,9 @@ class CourseGradingModel(object):
|
|||||||
|
|
||||||
descriptor = get_modulestore(location).get_item(location)
|
descriptor = get_modulestore(location).get_item(location)
|
||||||
return {
|
return {
|
||||||
"graderType" : descriptor.metadata.get('format', u"Not Graded"),
|
"graderType": descriptor.metadata.get('format', u"Not Graded"),
|
||||||
"location" : location,
|
"location": location,
|
||||||
"id" : 99 # just an arbitrary value to
|
"id": 99 # just an arbitrary value to
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -245,11 +245,11 @@ class CourseGradingModel(object):
|
|||||||
def parse_grader(json_grader):
|
def parse_grader(json_grader):
|
||||||
# manual to clear out kruft
|
# manual to clear out kruft
|
||||||
result = {
|
result = {
|
||||||
"type" : json_grader["type"],
|
"type": json_grader["type"],
|
||||||
"min_count" : int(json_grader.get('min_count', 0)),
|
"min_count": int(json_grader.get('min_count', 0)),
|
||||||
"drop_count" : int(json_grader.get('drop_count', 0)),
|
"drop_count": int(json_grader.get('drop_count', 0)),
|
||||||
"short_label" : json_grader.get('short_label', None),
|
"short_label": json_grader.get('short_label', None),
|
||||||
"weight" : float(json_grader.get('weight', 0)) / 100.0
|
"weight": float(json_grader.get('weight', 0)) / 100.0
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ MITX_FEATURES = {
|
|||||||
'USE_DJANGO_PIPELINE': True,
|
'USE_DJANGO_PIPELINE': True,
|
||||||
'GITHUB_PUSH': False,
|
'GITHUB_PUSH': False,
|
||||||
'ENABLE_DISCUSSION_SERVICE': False,
|
'ENABLE_DISCUSSION_SERVICE': False,
|
||||||
'AUTH_USE_MIT_CERTIFICATES' : False,
|
'AUTH_USE_MIT_CERTIFICATES': False,
|
||||||
'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
|
||||||
}
|
}
|
||||||
ENABLE_JASMINE = False
|
ENABLE_JASMINE = False
|
||||||
@@ -229,7 +229,7 @@ PIPELINE_JS = {
|
|||||||
'source_filenames': sorted(
|
'source_filenames': sorted(
|
||||||
rooted_glob(COMMON_ROOT / 'static/', 'coffee/src/**/*.coffee') +
|
rooted_glob(COMMON_ROOT / 'static/', 'coffee/src/**/*.coffee') +
|
||||||
rooted_glob(PROJECT_ROOT / 'static/', 'coffee/src/**/*.coffee')
|
rooted_glob(PROJECT_ROOT / 'static/', 'coffee/src/**/*.coffee')
|
||||||
) + [ 'js/hesitate.js', 'js/base.js'],
|
) + ['js/hesitate.js', 'js/base.js'],
|
||||||
'output_filename': 'js/cms-application.js',
|
'output_filename': 'js/cms-application.js',
|
||||||
},
|
},
|
||||||
'module-js': {
|
'module-js': {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ TEMPLATE_DEBUG = DEBUG
|
|||||||
LOGGING = get_logger_config(ENV_ROOT / "log",
|
LOGGING = get_logger_config(ENV_ROOT / "log",
|
||||||
logging_env="dev",
|
logging_env="dev",
|
||||||
tracking_filename="tracking.log",
|
tracking_filename="tracking.log",
|
||||||
dev_env = True,
|
dev_env=True,
|
||||||
debug=True)
|
debug=True)
|
||||||
|
|
||||||
modulestore_options = {
|
modulestore_options = {
|
||||||
@@ -41,7 +41,7 @@ CONTENTSTORE = {
|
|||||||
'ENGINE': 'xmodule.contentstore.mongo.MongoContentStore',
|
'ENGINE': 'xmodule.contentstore.mongo.MongoContentStore',
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'host': 'localhost',
|
'host': 'localhost',
|
||||||
'db' : 'xcontent',
|
'db': 'xcontent',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import socket
|
|||||||
|
|
||||||
MITX_FEATURES['AUTH_USE_MIT_CERTIFICATES'] = True
|
MITX_FEATURES['AUTH_USE_MIT_CERTIFICATES'] = True
|
||||||
|
|
||||||
MITX_FEATURES['USE_DJANGO_PIPELINE']=False # don't recompile scss
|
MITX_FEATURES['USE_DJANGO_PIPELINE'] = False # don't recompile scss
|
||||||
|
|
||||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https') # django 1.4 for nginx ssl proxy
|
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https') # django 1.4 for nginx ssl proxy
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ CONTENTSTORE = {
|
|||||||
'ENGINE': 'xmodule.contentstore.mongo.MongoContentStore',
|
'ENGINE': 'xmodule.contentstore.mongo.MongoContentStore',
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'host': 'localhost',
|
'host': 'localhost',
|
||||||
'db' : 'xcontent',
|
'db': 'xcontent',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from django.db import DEFAULT_DB_ALIAS
|
|||||||
from . import app_settings
|
from . import app_settings
|
||||||
from xmodule.contentstore.content import StaticContent
|
from xmodule.contentstore.content import StaticContent
|
||||||
|
|
||||||
|
|
||||||
def get_instance(model, instance_or_pk, timeout=None, using=None):
|
def get_instance(model, instance_or_pk, timeout=None, using=None):
|
||||||
"""
|
"""
|
||||||
Returns the ``model`` instance with a primary key of ``instance_or_pk``.
|
Returns the ``model`` instance with a primary key of ``instance_or_pk``.
|
||||||
@@ -108,11 +109,14 @@ def instance_key(model, instance_or_pk):
|
|||||||
getattr(instance_or_pk, 'pk', instance_or_pk),
|
getattr(instance_or_pk, 'pk', instance_or_pk),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def set_cached_content(content):
|
def set_cached_content(content):
|
||||||
cache.set(str(content.location), content)
|
cache.set(str(content.location), content)
|
||||||
|
|
||||||
|
|
||||||
def get_cached_content(location):
|
def get_cached_content(location):
|
||||||
return cache.get(str(location))
|
return cache.get(str(location))
|
||||||
|
|
||||||
|
|
||||||
def del_cached_content(location):
|
def del_cached_content(location):
|
||||||
cache.delete(str(location))
|
cache.delete(str(location))
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from xmodule.exceptions import NotFoundError
|
|||||||
class StaticContentServer(object):
|
class StaticContentServer(object):
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
# look to see if the request is prefixed with 'c4x' tag
|
# look to see if the request is prefixed with 'c4x' tag
|
||||||
if request.path.startswith('/' + XASSET_LOCATION_TAG +'/'):
|
if request.path.startswith('/' + XASSET_LOCATION_TAG + '/'):
|
||||||
loc = StaticContent.get_location_from_path(request.path)
|
loc = StaticContent.get_location_from_path(request.path)
|
||||||
# first look in our cache so we don't have to round-trip to the DB
|
# first look in our cache so we don't have to round-trip to the DB
|
||||||
content = get_cached_content(loc)
|
content = get_cached_content(loc)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from .models import CourseUserGroup
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def is_course_cohorted(course_id):
|
def is_course_cohorted(course_id):
|
||||||
"""
|
"""
|
||||||
Given a course id, return a boolean for whether or not the course is
|
Given a course id, return a boolean for whether or not the course is
|
||||||
@@ -115,6 +116,7 @@ def get_course_cohorts(course_id):
|
|||||||
|
|
||||||
### Helpers for cohort management views
|
### Helpers for cohort management views
|
||||||
|
|
||||||
|
|
||||||
def get_cohort_by_name(course_id, name):
|
def get_cohort_by_name(course_id, name):
|
||||||
"""
|
"""
|
||||||
Return the CourseUserGroup object for the given cohort. Raises DoesNotExist
|
Return the CourseUserGroup object for the given cohort. Raises DoesNotExist
|
||||||
@@ -124,6 +126,7 @@ def get_cohort_by_name(course_id, name):
|
|||||||
group_type=CourseUserGroup.COHORT,
|
group_type=CourseUserGroup.COHORT,
|
||||||
name=name)
|
name=name)
|
||||||
|
|
||||||
|
|
||||||
def get_cohort_by_id(course_id, cohort_id):
|
def get_cohort_by_id(course_id, cohort_id):
|
||||||
"""
|
"""
|
||||||
Return the CourseUserGroup object for the given cohort. Raises DoesNotExist
|
Return the CourseUserGroup object for the given cohort. Raises DoesNotExist
|
||||||
@@ -133,6 +136,7 @@ def get_cohort_by_id(course_id, cohort_id):
|
|||||||
group_type=CourseUserGroup.COHORT,
|
group_type=CourseUserGroup.COHORT,
|
||||||
id=cohort_id)
|
id=cohort_id)
|
||||||
|
|
||||||
|
|
||||||
def add_cohort(course_id, name):
|
def add_cohort(course_id, name):
|
||||||
"""
|
"""
|
||||||
Add a cohort to a course. Raises ValueError if a cohort of the same name already
|
Add a cohort to a course. Raises ValueError if a cohort of the same name already
|
||||||
@@ -148,12 +152,14 @@ def add_cohort(course_id, name):
|
|||||||
group_type=CourseUserGroup.COHORT,
|
group_type=CourseUserGroup.COHORT,
|
||||||
name=name)
|
name=name)
|
||||||
|
|
||||||
|
|
||||||
class CohortConflict(Exception):
|
class CohortConflict(Exception):
|
||||||
"""
|
"""
|
||||||
Raised when user to be added is already in another cohort in same course.
|
Raised when user to be added is already in another cohort in same course.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def add_user_to_cohort(cohort, username_or_email):
|
def add_user_to_cohort(cohort, username_or_email):
|
||||||
"""
|
"""
|
||||||
Look up the given user, and if successful, add them to the specified cohort.
|
Look up the given user, and if successful, add them to the specified cohort.
|
||||||
@@ -211,4 +217,3 @@ def delete_empty_cohort(course_id, name):
|
|||||||
name, course_id))
|
name, course_id))
|
||||||
|
|
||||||
cohort.delete()
|
cohort.delete()
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from django.db import models
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class CourseUserGroup(models.Model):
|
class CourseUserGroup(models.Model):
|
||||||
"""
|
"""
|
||||||
This model represents groups of users in a course. Groups may have different types,
|
This model represents groups of users in a course. Groups may have different types,
|
||||||
@@ -30,5 +31,3 @@ class CourseUserGroup(models.Model):
|
|||||||
COHORT = 'cohort'
|
COHORT = 'cohort'
|
||||||
GROUP_TYPE_CHOICES = ((COHORT, 'Cohort'),)
|
GROUP_TYPE_CHOICES = ((COHORT, 'Cohort'),)
|
||||||
group_type = models.CharField(max_length=20, choices=GROUP_TYPE_CHOICES)
|
group_type = models.CharField(max_length=20, choices=GROUP_TYPE_CHOICES)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from xmodule.modulestore.django import modulestore, _MODULESTORES
|
|||||||
# manually overriding the modulestore. However, running with
|
# manually overriding the modulestore. However, running with
|
||||||
# cms.envs.test doesn't.
|
# cms.envs.test doesn't.
|
||||||
|
|
||||||
|
|
||||||
def xml_store_config(data_dir):
|
def xml_store_config(data_dir):
|
||||||
return {
|
return {
|
||||||
'default': {
|
'default': {
|
||||||
@@ -28,6 +29,7 @@ def xml_store_config(data_dir):
|
|||||||
TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
|
TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
|
||||||
TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR)
|
TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR)
|
||||||
|
|
||||||
|
|
||||||
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
|
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
|
||||||
class TestCohorts(django.test.TestCase):
|
class TestCohorts(django.test.TestCase):
|
||||||
|
|
||||||
@@ -184,6 +186,3 @@ class TestCohorts(django.test.TestCase):
|
|||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
is_commentable_cohorted(course.id, to_id("Feedback")),
|
is_commentable_cohorted(course.id, to_id("Feedback")),
|
||||||
"Feedback was listed as cohorted. Should be.")
|
"Feedback was listed as cohorted. Should be.")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import track.views
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def json_http_response(data):
|
def json_http_response(data):
|
||||||
"""
|
"""
|
||||||
Return an HttpResponse with the data json-serialized and the right content
|
Return an HttpResponse with the data json-serialized and the right content
|
||||||
@@ -29,6 +30,7 @@ def json_http_response(data):
|
|||||||
"""
|
"""
|
||||||
return HttpResponse(json.dumps(data), content_type="application/json")
|
return HttpResponse(json.dumps(data), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
def split_by_comma_and_whitespace(s):
|
def split_by_comma_and_whitespace(s):
|
||||||
"""
|
"""
|
||||||
Split a string both by commas and whitespice. Returns a list.
|
Split a string both by commas and whitespice. Returns a list.
|
||||||
@@ -177,6 +179,7 @@ def add_users_to_cohort(request, course_id, cohort_id):
|
|||||||
'conflict': conflict,
|
'conflict': conflict,
|
||||||
'unknown': unknown})
|
'unknown': unknown})
|
||||||
|
|
||||||
|
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
@require_POST
|
@require_POST
|
||||||
def remove_user_from_cohort(request, course_id, cohort_id):
|
def remove_user_from_cohort(request, course_id, cohort_id):
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ django admin pages for courseware model
|
|||||||
from external_auth.models import *
|
from external_auth.models import *
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
|
|
||||||
class ExternalAuthMapAdmin(admin.ModelAdmin):
|
class ExternalAuthMapAdmin(admin.ModelAdmin):
|
||||||
search_fields = ['external_id','user__username']
|
search_fields = ['external_id', 'user__username']
|
||||||
date_hierarchy = 'dtcreated'
|
date_hierarchy = 'dtcreated'
|
||||||
|
|
||||||
admin.site.register(ExternalAuthMap, ExternalAuthMapAdmin)
|
admin.site.register(ExternalAuthMap, ExternalAuthMapAdmin)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ file and check it in at the same time as your model changes. To do that,
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
|
||||||
class ExternalAuthMap(models.Model):
|
class ExternalAuthMap(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = (('external_id', 'external_domain'), )
|
unique_together = (('external_id', 'external_domain'), )
|
||||||
@@ -19,13 +20,12 @@ class ExternalAuthMap(models.Model):
|
|||||||
external_domain = models.CharField(max_length=255, db_index=True)
|
external_domain = models.CharField(max_length=255, db_index=True)
|
||||||
external_credentials = models.TextField(blank=True) # JSON dictionary
|
external_credentials = models.TextField(blank=True) # JSON dictionary
|
||||||
external_email = models.CharField(max_length=255, db_index=True)
|
external_email = models.CharField(max_length=255, db_index=True)
|
||||||
external_name = models.CharField(blank=True,max_length=255, db_index=True)
|
external_name = models.CharField(blank=True, max_length=255, db_index=True)
|
||||||
user = models.OneToOneField(User, unique=True, db_index=True, null=True)
|
user = models.OneToOneField(User, unique=True, db_index=True, null=True)
|
||||||
internal_password = models.CharField(blank=True, max_length=31) # randomly generated
|
internal_password = models.CharField(blank=True, max_length=31) # randomly generated
|
||||||
dtcreated = models.DateTimeField('creation date',auto_now_add=True)
|
dtcreated = models.DateTimeField('creation date', auto_now_add=True)
|
||||||
dtsignup = models.DateTimeField('signup date',null=True) # set after signup
|
dtsignup = models.DateTimeField('signup date', null=True) # set after signup
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
s = "[%s] = (%s / %s)" % (self.external_id, self.external_name, self.external_email)
|
s = "[%s] = (%s / %s)" % (self.external_id, self.external_name, self.external_email)
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from django.test import TestCase, LiveServerTestCase
|
|||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
|
|
||||||
|
|
||||||
class MyFetcher(HTTPFetcher):
|
class MyFetcher(HTTPFetcher):
|
||||||
"""A fetcher that uses server-internal calls for performing HTTP
|
"""A fetcher that uses server-internal calls for performing HTTP
|
||||||
requests.
|
requests.
|
||||||
@@ -60,6 +61,7 @@ class MyFetcher(HTTPFetcher):
|
|||||||
status=status,
|
status=status,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class OpenIdProviderTest(TestCase):
|
class OpenIdProviderTest(TestCase):
|
||||||
|
|
||||||
# def setUp(self):
|
# def setUp(self):
|
||||||
@@ -78,7 +80,7 @@ class OpenIdProviderTest(TestCase):
|
|||||||
provider_url = reverse('openid-provider-xrds')
|
provider_url = reverse('openid-provider-xrds')
|
||||||
factory = RequestFactory()
|
factory = RequestFactory()
|
||||||
request = factory.request()
|
request = factory.request()
|
||||||
abs_provider_url = request.build_absolute_uri(location = provider_url)
|
abs_provider_url = request.build_absolute_uri(location=provider_url)
|
||||||
|
|
||||||
# In order for this absolute URL to work (i.e. to get xrds, then authentication)
|
# In order for this absolute URL to work (i.e. to get xrds, then authentication)
|
||||||
# in the test environment, we either need a live server that works with the default
|
# in the test environment, we either need a live server that works with the default
|
||||||
@@ -89,7 +91,7 @@ class OpenIdProviderTest(TestCase):
|
|||||||
|
|
||||||
# now we can begin the login process by invoking a local openid client,
|
# now we can begin the login process by invoking a local openid client,
|
||||||
# with a pointer to the (also-local) openid provider:
|
# with a pointer to the (also-local) openid provider:
|
||||||
with self.settings(OPENID_SSO_SERVER_URL = abs_provider_url):
|
with self.settings(OPENID_SSO_SERVER_URL=abs_provider_url):
|
||||||
url = reverse('openid-login')
|
url = reverse('openid-login')
|
||||||
resp = self.client.post(url)
|
resp = self.client.post(url)
|
||||||
code = 200
|
code = 200
|
||||||
@@ -107,7 +109,7 @@ class OpenIdProviderTest(TestCase):
|
|||||||
provider_url = reverse('openid-provider-login')
|
provider_url = reverse('openid-provider-login')
|
||||||
factory = RequestFactory()
|
factory = RequestFactory()
|
||||||
request = factory.request()
|
request = factory.request()
|
||||||
abs_provider_url = request.build_absolute_uri(location = provider_url)
|
abs_provider_url = request.build_absolute_uri(location=provider_url)
|
||||||
|
|
||||||
# In order for this absolute URL to work (i.e. to get xrds, then authentication)
|
# In order for this absolute URL to work (i.e. to get xrds, then authentication)
|
||||||
# in the test environment, we either need a live server that works with the default
|
# in the test environment, we either need a live server that works with the default
|
||||||
@@ -118,7 +120,7 @@ class OpenIdProviderTest(TestCase):
|
|||||||
|
|
||||||
# now we can begin the login process by invoking a local openid client,
|
# now we can begin the login process by invoking a local openid client,
|
||||||
# with a pointer to the (also-local) openid provider:
|
# with a pointer to the (also-local) openid provider:
|
||||||
with self.settings(OPENID_SSO_SERVER_URL = abs_provider_url):
|
with self.settings(OPENID_SSO_SERVER_URL=abs_provider_url):
|
||||||
url = reverse('openid-login')
|
url = reverse('openid-login')
|
||||||
resp = self.client.post(url)
|
resp = self.client.post(url)
|
||||||
code = 200
|
code = 200
|
||||||
@@ -154,24 +156,24 @@ class OpenIdProviderTest(TestCase):
|
|||||||
return
|
return
|
||||||
url = reverse('openid-provider-login')
|
url = reverse('openid-provider-login')
|
||||||
post_args = {
|
post_args = {
|
||||||
"openid.mode" : "checkid_setup",
|
"openid.mode": "checkid_setup",
|
||||||
"openid.return_to" : "http://testserver/openid/complete/?janrain_nonce=2013-01-23T06%3A20%3A17ZaN7j6H",
|
"openid.return_to": "http://testserver/openid/complete/?janrain_nonce=2013-01-23T06%3A20%3A17ZaN7j6H",
|
||||||
"openid.assoc_handle" : "{HMAC-SHA1}{50ff8120}{rh87+Q==}",
|
"openid.assoc_handle": "{HMAC-SHA1}{50ff8120}{rh87+Q==}",
|
||||||
"openid.claimed_id" : "http://specs.openid.net/auth/2.0/identifier_select",
|
"openid.claimed_id": "http://specs.openid.net/auth/2.0/identifier_select",
|
||||||
"openid.ns" : "http://specs.openid.net/auth/2.0",
|
"openid.ns": "http://specs.openid.net/auth/2.0",
|
||||||
"openid.realm" : "http://testserver/",
|
"openid.realm": "http://testserver/",
|
||||||
"openid.identity" : "http://specs.openid.net/auth/2.0/identifier_select",
|
"openid.identity": "http://specs.openid.net/auth/2.0/identifier_select",
|
||||||
"openid.ns.ax" : "http://openid.net/srv/ax/1.0",
|
"openid.ns.ax": "http://openid.net/srv/ax/1.0",
|
||||||
"openid.ax.mode" : "fetch_request",
|
"openid.ax.mode": "fetch_request",
|
||||||
"openid.ax.required" : "email,fullname,old_email,firstname,old_nickname,lastname,old_fullname,nickname",
|
"openid.ax.required": "email,fullname,old_email,firstname,old_nickname,lastname,old_fullname,nickname",
|
||||||
"openid.ax.type.fullname" : "http://axschema.org/namePerson",
|
"openid.ax.type.fullname": "http://axschema.org/namePerson",
|
||||||
"openid.ax.type.lastname" : "http://axschema.org/namePerson/last",
|
"openid.ax.type.lastname": "http://axschema.org/namePerson/last",
|
||||||
"openid.ax.type.firstname" : "http://axschema.org/namePerson/first",
|
"openid.ax.type.firstname": "http://axschema.org/namePerson/first",
|
||||||
"openid.ax.type.nickname" : "http://axschema.org/namePerson/friendly",
|
"openid.ax.type.nickname": "http://axschema.org/namePerson/friendly",
|
||||||
"openid.ax.type.email" : "http://axschema.org/contact/email",
|
"openid.ax.type.email": "http://axschema.org/contact/email",
|
||||||
"openid.ax.type.old_email" : "http://schema.openid.net/contact/email",
|
"openid.ax.type.old_email": "http://schema.openid.net/contact/email",
|
||||||
"openid.ax.type.old_nickname" : "http://schema.openid.net/namePerson/friendly",
|
"openid.ax.type.old_nickname": "http://schema.openid.net/namePerson/friendly",
|
||||||
"openid.ax.type.old_fullname" : "http://schema.openid.net/namePerson",
|
"openid.ax.type.old_fullname": "http://schema.openid.net/namePerson",
|
||||||
}
|
}
|
||||||
resp = self.client.post(url, post_args)
|
resp = self.client.post(url, post_args)
|
||||||
code = 200
|
code = 200
|
||||||
@@ -196,11 +198,11 @@ class OpenIdProviderLiveServerTest(LiveServerTestCase):
|
|||||||
provider_url = reverse('openid-provider-xrds')
|
provider_url = reverse('openid-provider-xrds')
|
||||||
factory = RequestFactory()
|
factory = RequestFactory()
|
||||||
request = factory.request()
|
request = factory.request()
|
||||||
abs_provider_url = request.build_absolute_uri(location = provider_url)
|
abs_provider_url = request.build_absolute_uri(location=provider_url)
|
||||||
|
|
||||||
# now we can begin the login process by invoking a local openid client,
|
# now we can begin the login process by invoking a local openid client,
|
||||||
# with a pointer to the (also-local) openid provider:
|
# with a pointer to the (also-local) openid provider:
|
||||||
with self.settings(OPENID_SSO_SERVER_URL = abs_provider_url):
|
with self.settings(OPENID_SSO_SERVER_URL=abs_provider_url):
|
||||||
url = reverse('openid-login')
|
url = reverse('openid-login')
|
||||||
resp = self.client.post(url)
|
resp = self.client.post(url)
|
||||||
code = 200
|
code = 200
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import mitxmako.middleware
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class MakoLoader(object):
|
class MakoLoader(object):
|
||||||
"""
|
"""
|
||||||
This is a Django loader object which will load the template as a
|
This is a Django loader object which will load the template as a
|
||||||
@@ -71,6 +72,7 @@ class MakoFilesystemLoader(MakoLoader):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
MakoLoader.__init__(self, FilesystemLoader())
|
MakoLoader.__init__(self, FilesystemLoader())
|
||||||
|
|
||||||
|
|
||||||
class MakoAppDirectoriesLoader(MakoLoader):
|
class MakoAppDirectoriesLoader(MakoLoader):
|
||||||
is_usable = True
|
is_usable = True
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ from mitxmako import middleware
|
|||||||
django_variables = ['lookup', 'output_encoding', 'encoding_errors']
|
django_variables = ['lookup', 'output_encoding', 'encoding_errors']
|
||||||
|
|
||||||
# TODO: We should make this a Django Template subclass that simply has the MakoTemplate inside of it? (Intead of inheriting from MakoTemplate)
|
# TODO: We should make this a Django Template subclass that simply has the MakoTemplate inside of it? (Intead of inheriting from MakoTemplate)
|
||||||
|
|
||||||
|
|
||||||
class Template(MakoTemplate):
|
class Template(MakoTemplate):
|
||||||
"""
|
"""
|
||||||
This bridges the gap between a Mako template and a djano template. It can
|
This bridges the gap between a Mako template and a djano template. It can
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from django.template import loader
|
|||||||
from django.template.base import Template, Context
|
from django.template.base import Template, Context
|
||||||
from django.template.loader import get_template, select_template
|
from django.template.loader import get_template, select_template
|
||||||
|
|
||||||
|
|
||||||
def django_template_include(file_name, mako_context):
|
def django_template_include(file_name, mako_context):
|
||||||
"""
|
"""
|
||||||
This can be used within a mako template to include a django template
|
This can be used within a mako template to include a django template
|
||||||
@@ -9,7 +10,7 @@ def django_template_include(file_name, mako_context):
|
|||||||
which can be the mako context ('context') or a dictionary.
|
which can be the mako context ('context') or a dictionary.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
dictionary = dict( mako_context )
|
dictionary = dict(mako_context)
|
||||||
return loader.render_to_string(file_name, dictionary=dictionary)
|
return loader.render_to_string(file_name, dictionary=dictionary)
|
||||||
|
|
||||||
|
|
||||||
@@ -49,5 +50,3 @@ def render_inclusion(func, file_name, takes_context, django_context, *args, **kw
|
|||||||
new_context['csrf_token'] = csrf_token
|
new_context['csrf_token'] = csrf_token
|
||||||
|
|
||||||
return nodelist.render(new_context)
|
return nodelist.render(new_context)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ def _url_replace_regex(prefix):
|
|||||||
(?P=quote) # the first matching closing quote
|
(?P=quote) # the first matching closing quote
|
||||||
""".format(prefix=prefix)
|
""".format(prefix=prefix)
|
||||||
|
|
||||||
|
|
||||||
def try_staticfiles_lookup(path):
|
def try_staticfiles_lookup(path):
|
||||||
"""
|
"""
|
||||||
Try to lookup a path in staticfiles_storage. If it fails, return
|
Try to lookup a path in staticfiles_storage. If it fails, return
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ def test_mongo_filestore(mock_modulestore, mock_static_content):
|
|||||||
|
|
||||||
mock_static_content.convert_legacy_static_url.assert_called_once_with('file.png', NAMESPACE)
|
mock_static_content.convert_legacy_static_url.assert_called_once_with('file.png', NAMESPACE)
|
||||||
|
|
||||||
|
|
||||||
@patch('static_replace.settings')
|
@patch('static_replace.settings')
|
||||||
@patch('static_replace.modulestore')
|
@patch('static_replace.modulestore')
|
||||||
@patch('static_replace.staticfiles_storage')
|
@patch('static_replace.staticfiles_storage')
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import sys
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_site_status_msg(course_id):
|
def get_site_status_msg(course_id):
|
||||||
"""
|
"""
|
||||||
Look for a file settings.STATUS_MESSAGE_PATH. If found, read it,
|
Look for a file settings.STATUS_MESSAGE_PATH. If found, read it,
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ from student.userprofile. '''
|
|||||||
d[key] = item
|
d[key] = item
|
||||||
return d
|
return d
|
||||||
|
|
||||||
extracted = [{'up':extract_dict(up_keys, t[0]), 'u':extract_dict(user_keys, t[1])} for t in user_tuples]
|
extracted = [{'up': extract_dict(up_keys, t[0]), 'u':extract_dict(user_keys, t[1])} for t in user_tuples]
|
||||||
fp = open('transfer_users.txt', 'w')
|
fp = open('transfer_users.txt', 'w')
|
||||||
json.dump(extracted, fp)
|
json.dump(extracted, fp)
|
||||||
fp.close()
|
fp.close()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from optparse import make_option
|
|||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
from django.contrib.auth.models import User, Group
|
from django.contrib.auth.models import User, Group
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
option_list = BaseCommand.option_list + (
|
option_list = BaseCommand.option_list + (
|
||||||
make_option('--list',
|
make_option('--list',
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from student.models import UserProfile, CourseEnrollment
|
|||||||
|
|
||||||
from student.views import _do_create_account, get_random_post_override
|
from student.views import _do_create_account, get_random_post_override
|
||||||
|
|
||||||
|
|
||||||
def create(n, course_id):
|
def create(n, course_id):
|
||||||
"""Create n users, enrolling them in course_id if it's not None"""
|
"""Create n users, enrolling them in course_id if it's not None"""
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
@@ -15,6 +16,7 @@ def create(n, course_id):
|
|||||||
if course_id is not None:
|
if course_id is not None:
|
||||||
CourseEnrollment.objects.create(user=user, course_id=course_id)
|
CourseEnrollment.objects.create(user=user, course_id=course_id)
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = """Create N new users, with random parameters.
|
help = """Create N new users, with random parameters.
|
||||||
|
|
||||||
|
|||||||
@@ -49,19 +49,19 @@ class Command(BaseCommand):
|
|||||||
for registration in registrations:
|
for registration in registrations:
|
||||||
if 'accommodation_pending' in options and options['accommodation_pending'] and not registration.accommodation_is_pending:
|
if 'accommodation_pending' in options and options['accommodation_pending'] and not registration.accommodation_is_pending:
|
||||||
continue
|
continue
|
||||||
record = {'username' : registration.testcenter_user.user.username,
|
record = {'username': registration.testcenter_user.user.username,
|
||||||
'email' : registration.testcenter_user.email,
|
'email': registration.testcenter_user.email,
|
||||||
'first_name' : registration.testcenter_user.first_name,
|
'first_name': registration.testcenter_user.first_name,
|
||||||
'last_name' : registration.testcenter_user.last_name,
|
'last_name': registration.testcenter_user.last_name,
|
||||||
'client_candidate_id' : registration.client_candidate_id,
|
'client_candidate_id': registration.client_candidate_id,
|
||||||
'client_authorization_id' : registration.client_authorization_id,
|
'client_authorization_id': registration.client_authorization_id,
|
||||||
'course_id' : registration.course_id,
|
'course_id': registration.course_id,
|
||||||
'exam_series_code' : registration.exam_series_code,
|
'exam_series_code': registration.exam_series_code,
|
||||||
'accommodation_request' : registration.accommodation_request,
|
'accommodation_request': registration.accommodation_request,
|
||||||
'accommodation_code' : registration.accommodation_code,
|
'accommodation_code': registration.accommodation_code,
|
||||||
'registration_status' : registration.registration_status(),
|
'registration_status': registration.registration_status(),
|
||||||
'demographics_status' : registration.demographics_status(),
|
'demographics_status': registration.demographics_status(),
|
||||||
'accommodation_status' : registration.accommodation_status(),
|
'accommodation_status': registration.accommodation_status(),
|
||||||
}
|
}
|
||||||
if len(registration.upload_error_message) > 0:
|
if len(registration.upload_error_message) > 0:
|
||||||
record['registration_error'] = registration.upload_error_message
|
record['registration_error'] = registration.upload_error_message
|
||||||
@@ -75,4 +75,3 @@ class Command(BaseCommand):
|
|||||||
# dump output:
|
# dump output:
|
||||||
with open(outputfile, 'w') as outfile:
|
with open(outputfile, 'w') as outfile:
|
||||||
dump(output, outfile, indent=2)
|
dump(output, outfile, indent=2)
|
||||||
|
|
||||||
|
|||||||
@@ -116,4 +116,3 @@ class Command(BaseCommand):
|
|||||||
tcuser.save()
|
tcuser.save()
|
||||||
except TestCenterUser.DoesNotExist:
|
except TestCenterUser.DoesNotExist:
|
||||||
Command.datadog_error(" Failed to find record for client_candidate_id {}".format(client_candidate_id), vcdcfile.name)
|
Command.datadog_error(" Failed to find record for client_candidate_id {}".format(client_candidate_id), vcdcfile.name)
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from student.views import course_from_id
|
|||||||
from xmodule.course_module import CourseDescriptor
|
from xmodule.course_module import CourseDescriptor
|
||||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
option_list = BaseCommand.option_list + (
|
option_list = BaseCommand.option_list + (
|
||||||
# registration info:
|
# registration info:
|
||||||
@@ -120,9 +121,9 @@ class Command(BaseCommand):
|
|||||||
else:
|
else:
|
||||||
# otherwise use explicit values (so we don't have to define a course):
|
# otherwise use explicit values (so we don't have to define a course):
|
||||||
exam_name = "Dummy Placeholder Name"
|
exam_name = "Dummy Placeholder Name"
|
||||||
exam_info = { 'Exam_Series_Code': our_options['exam_series_code'],
|
exam_info = {'Exam_Series_Code': our_options['exam_series_code'],
|
||||||
'First_Eligible_Appointment_Date' : our_options['eligibility_appointment_date_first'],
|
'First_Eligible_Appointment_Date': our_options['eligibility_appointment_date_first'],
|
||||||
'Last_Eligible_Appointment_Date' : our_options['eligibility_appointment_date_last'],
|
'Last_Eligible_Appointment_Date': our_options['eligibility_appointment_date_last'],
|
||||||
}
|
}
|
||||||
exam = CourseDescriptor.TestCenterExam(course_id, exam_name, exam_info)
|
exam = CourseDescriptor.TestCenterExam(course_id, exam_name, exam_info)
|
||||||
# update option values for date_first and date_last to use YYYY-MM-DD format
|
# update option values for date_first and date_last to use YYYY-MM-DD format
|
||||||
@@ -135,7 +136,7 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
exam_code = exam.exam_series_code
|
exam_code = exam.exam_series_code
|
||||||
|
|
||||||
UPDATE_FIELDS = ( 'accommodation_request',
|
UPDATE_FIELDS = ('accommodation_request',
|
||||||
'accommodation_code',
|
'accommodation_code',
|
||||||
'client_authorization_id',
|
'client_authorization_id',
|
||||||
'exam_series_code',
|
'exam_series_code',
|
||||||
@@ -152,7 +153,7 @@ class Command(BaseCommand):
|
|||||||
if fieldname in our_options and registration.__getattribute__(fieldname) != our_options[fieldname]:
|
if fieldname in our_options and registration.__getattribute__(fieldname) != our_options[fieldname]:
|
||||||
needs_updating = True;
|
needs_updating = True;
|
||||||
else:
|
else:
|
||||||
accommodation_request = our_options.get('accommodation_request','')
|
accommodation_request = our_options.get('accommodation_request', '')
|
||||||
registration = TestCenterRegistration.create(testcenter_user, exam, accommodation_request)
|
registration = TestCenterRegistration.create(testcenter_user, exam, accommodation_request)
|
||||||
needs_updating = True
|
needs_updating = True
|
||||||
|
|
||||||
@@ -194,7 +195,7 @@ class Command(BaseCommand):
|
|||||||
if 'exam_series_code' in our_options:
|
if 'exam_series_code' in our_options:
|
||||||
exam_code = our_options['exam_series_code']
|
exam_code = our_options['exam_series_code']
|
||||||
registration = get_testcenter_registration(student, course_id, exam_code)[0]
|
registration = get_testcenter_registration(student, course_id, exam_code)[0]
|
||||||
for internal_field in [ 'upload_error_message', 'upload_status', 'authorization_id']:
|
for internal_field in ['upload_error_message', 'upload_status', 'authorization_id']:
|
||||||
if internal_field in our_options:
|
if internal_field in our_options:
|
||||||
registration.__setattr__(internal_field, our_options[internal_field])
|
registration.__setattr__(internal_field, our_options[internal_field])
|
||||||
change_internal = True
|
change_internal = True
|
||||||
@@ -204,5 +205,3 @@ class Command(BaseCommand):
|
|||||||
registration.save()
|
registration.save()
|
||||||
else:
|
else:
|
||||||
print "No changes necessary to make to confirmation information in existing user's registration."
|
print "No changes necessary to make to confirmation information in existing user's registration."
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from django.core.management.base import BaseCommand, CommandError
|
|||||||
|
|
||||||
from student.models import TestCenterUser, TestCenterUserForm
|
from student.models import TestCenterUser, TestCenterUserForm
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
option_list = BaseCommand.option_list + (
|
option_list = BaseCommand.option_list + (
|
||||||
# demographics:
|
# demographics:
|
||||||
@@ -177,7 +178,7 @@ class Command(BaseCommand):
|
|||||||
# override internal values:
|
# override internal values:
|
||||||
change_internal = False
|
change_internal = False
|
||||||
testcenter_user = TestCenterUser.objects.get(user=student)
|
testcenter_user = TestCenterUser.objects.get(user=student)
|
||||||
for internal_field in [ 'upload_error_message', 'upload_status', 'client_candidate_id']:
|
for internal_field in ['upload_error_message', 'upload_status', 'client_candidate_id']:
|
||||||
if internal_field in our_options:
|
if internal_field in our_options:
|
||||||
testcenter_user.__setattr__(internal_field, our_options[internal_field])
|
testcenter_user.__setattr__(internal_field, our_options[internal_field])
|
||||||
change_internal = True
|
change_internal = True
|
||||||
@@ -187,4 +188,3 @@ class Command(BaseCommand):
|
|||||||
print "Updated confirmation information in existing user's demographics."
|
print "Updated confirmation information in existing user's demographics."
|
||||||
else:
|
else:
|
||||||
print "No changes necessary to make to confirmation information in existing user's demographics."
|
print "No changes necessary to make to confirmation information in existing user's demographics."
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
# check additional required settings for import and export:
|
# check additional required settings for import and export:
|
||||||
if options['mode'] in ('export', 'both'):
|
if options['mode'] in ('export', 'both'):
|
||||||
for value in ['LOCAL_EXPORT','SFTP_EXPORT']:
|
for value in ['LOCAL_EXPORT', 'SFTP_EXPORT']:
|
||||||
if value not in settings.PEARSON:
|
if value not in settings.PEARSON:
|
||||||
raise CommandError('No entry in the PEARSON settings'
|
raise CommandError('No entry in the PEARSON settings'
|
||||||
'(env/auth.json) for {0}'.format(value))
|
'(env/auth.json) for {0}'.format(value))
|
||||||
@@ -59,7 +59,7 @@ class Command(BaseCommand):
|
|||||||
os.makedirs(source_dir)
|
os.makedirs(source_dir)
|
||||||
|
|
||||||
if options['mode'] in ('import', 'both'):
|
if options['mode'] in ('import', 'both'):
|
||||||
for value in ['LOCAL_IMPORT','SFTP_IMPORT']:
|
for value in ['LOCAL_IMPORT', 'SFTP_IMPORT']:
|
||||||
if value not in settings.PEARSON:
|
if value not in settings.PEARSON:
|
||||||
raise CommandError('No entry in the PEARSON settings'
|
raise CommandError('No entry in the PEARSON settings'
|
||||||
'(env/auth.json) for {0}'.format(value))
|
'(env/auth.json) for {0}'.format(value))
|
||||||
@@ -135,17 +135,17 @@ class Command(BaseCommand):
|
|||||||
k.set_contents_from_filename(source_file)
|
k.set_contents_from_filename(source_file)
|
||||||
|
|
||||||
def export_pearson():
|
def export_pearson():
|
||||||
options = { 'dest-from-settings' : True }
|
options = {'dest-from-settings': True}
|
||||||
call_command('pearson_export_cdd', **options)
|
call_command('pearson_export_cdd', **options)
|
||||||
call_command('pearson_export_ead', **options)
|
call_command('pearson_export_ead', **options)
|
||||||
mode = 'export'
|
mode = 'export'
|
||||||
sftp(settings.PEARSON['LOCAL_EXPORT'], settings.PEARSON['SFTP_EXPORT'], mode, deleteAfterCopy = False)
|
sftp(settings.PEARSON['LOCAL_EXPORT'], settings.PEARSON['SFTP_EXPORT'], mode, deleteAfterCopy=False)
|
||||||
s3(settings.PEARSON['LOCAL_EXPORT'], settings.PEARSON['S3_BUCKET'], mode, deleteAfterCopy=True)
|
s3(settings.PEARSON['LOCAL_EXPORT'], settings.PEARSON['S3_BUCKET'], mode, deleteAfterCopy=True)
|
||||||
|
|
||||||
def import_pearson():
|
def import_pearson():
|
||||||
mode = 'import'
|
mode = 'import'
|
||||||
try:
|
try:
|
||||||
sftp(settings.PEARSON['SFTP_IMPORT'], settings.PEARSON['LOCAL_IMPORT'], mode, deleteAfterCopy = True)
|
sftp(settings.PEARSON['SFTP_IMPORT'], settings.PEARSON['LOCAL_IMPORT'], mode, deleteAfterCopy=True)
|
||||||
s3(settings.PEARSON['LOCAL_IMPORT'], settings.PEARSON['S3_BUCKET'], mode, deleteAfterCopy=False)
|
s3(settings.PEARSON['LOCAL_IMPORT'], settings.PEARSON['S3_BUCKET'], mode, deleteAfterCopy=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
dog_http_api.event('Pearson Import failure', str(e))
|
dog_http_api.event('Pearson Import failure', str(e))
|
||||||
|
|||||||
@@ -17,30 +17,31 @@ from student.models import User, TestCenterRegistration, TestCenterUser, get_tes
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def create_tc_user(username):
|
def create_tc_user(username):
|
||||||
user = User.objects.create_user(username, '{}@edx.org'.format(username), 'fakepass')
|
user = User.objects.create_user(username, '{}@edx.org'.format(username), 'fakepass')
|
||||||
options = {
|
options = {
|
||||||
'first_name' : 'TestFirst',
|
'first_name': 'TestFirst',
|
||||||
'last_name' : 'TestLast',
|
'last_name': 'TestLast',
|
||||||
'address_1' : 'Test Address',
|
'address_1': 'Test Address',
|
||||||
'city' : 'TestCity',
|
'city': 'TestCity',
|
||||||
'state' : 'Alberta',
|
'state': 'Alberta',
|
||||||
'postal_code' : 'A0B 1C2',
|
'postal_code': 'A0B 1C2',
|
||||||
'country' : 'CAN',
|
'country': 'CAN',
|
||||||
'phone' : '252-1866',
|
'phone': '252-1866',
|
||||||
'phone_country_code' : '1',
|
'phone_country_code': '1',
|
||||||
}
|
}
|
||||||
call_command('pearson_make_tc_user', username, **options)
|
call_command('pearson_make_tc_user', username, **options)
|
||||||
return TestCenterUser.objects.get(user=user)
|
return TestCenterUser.objects.get(user=user)
|
||||||
|
|
||||||
|
|
||||||
def create_tc_registration(username, course_id = 'org1/course1/term1', exam_code = 'exam1', accommodation_code = None):
|
def create_tc_registration(username, course_id='org1/course1/term1', exam_code='exam1', accommodation_code=None):
|
||||||
|
|
||||||
options = { 'exam_series_code' : exam_code,
|
options = {'exam_series_code': exam_code,
|
||||||
'eligibility_appointment_date_first' : '2013-01-01T00:00',
|
'eligibility_appointment_date_first': '2013-01-01T00:00',
|
||||||
'eligibility_appointment_date_last' : '2013-12-31T23:59',
|
'eligibility_appointment_date_last': '2013-12-31T23:59',
|
||||||
'accommodation_code' : accommodation_code,
|
'accommodation_code': accommodation_code,
|
||||||
'create_dummy_exam' : True,
|
'create_dummy_exam': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
call_command('pearson_make_tc_registration', username, course_id, **options)
|
call_command('pearson_make_tc_registration', username, course_id, **options)
|
||||||
@@ -48,21 +49,23 @@ def create_tc_registration(username, course_id = 'org1/course1/term1', exam_code
|
|||||||
registrations = get_testcenter_registration(user, course_id, exam_code)
|
registrations = get_testcenter_registration(user, course_id, exam_code)
|
||||||
return registrations[0]
|
return registrations[0]
|
||||||
|
|
||||||
|
|
||||||
def create_multiple_registrations(prefix='test'):
|
def create_multiple_registrations(prefix='test'):
|
||||||
username1 = '{}_multiple1'.format(prefix)
|
username1 = '{}_multiple1'.format(prefix)
|
||||||
create_tc_user(username1)
|
create_tc_user(username1)
|
||||||
create_tc_registration(username1)
|
create_tc_registration(username1)
|
||||||
create_tc_registration(username1, course_id = 'org1/course2/term1')
|
create_tc_registration(username1, course_id='org1/course2/term1')
|
||||||
create_tc_registration(username1, exam_code = 'exam2')
|
create_tc_registration(username1, exam_code='exam2')
|
||||||
username2 = '{}_multiple2'.format(prefix)
|
username2 = '{}_multiple2'.format(prefix)
|
||||||
create_tc_user(username2)
|
create_tc_user(username2)
|
||||||
create_tc_registration(username2)
|
create_tc_registration(username2)
|
||||||
username3 = '{}_multiple3'.format(prefix)
|
username3 = '{}_multiple3'.format(prefix)
|
||||||
create_tc_user(username3)
|
create_tc_user(username3)
|
||||||
create_tc_registration(username3, course_id = 'org1/course2/term1')
|
create_tc_registration(username3, course_id='org1/course2/term1')
|
||||||
username4 = '{}_multiple4'.format(prefix)
|
username4 = '{}_multiple4'.format(prefix)
|
||||||
create_tc_user(username4)
|
create_tc_user(username4)
|
||||||
create_tc_registration(username4, exam_code = 'exam2')
|
create_tc_registration(username4, exam_code='exam2')
|
||||||
|
|
||||||
|
|
||||||
def get_command_error_text(*args, **options):
|
def get_command_error_text(*args, **options):
|
||||||
stderr_string = None
|
stderr_string = None
|
||||||
@@ -90,6 +93,7 @@ def get_command_error_text(*args, **options):
|
|||||||
raise Exception("Expected call to {} to fail, but it succeeded!".format(args[0]))
|
raise Exception("Expected call to {} to fail, but it succeeded!".format(args[0]))
|
||||||
return stderr_string
|
return stderr_string
|
||||||
|
|
||||||
|
|
||||||
def get_error_string_for_management_call(*args, **options):
|
def get_error_string_for_management_call(*args, **options):
|
||||||
stdout_string = None
|
stdout_string = None
|
||||||
old_stdout = sys.stdout
|
old_stdout = sys.stdout
|
||||||
@@ -132,7 +136,8 @@ def get_file_info(dirpath):
|
|||||||
numlines = len(filecontents)
|
numlines = len(filecontents)
|
||||||
return filepath, numlines
|
return filepath, numlines
|
||||||
else:
|
else:
|
||||||
raise Exception("Expected to find a single file in {}, but found {}".format(dirpath,filelist))
|
raise Exception("Expected to find a single file in {}, but found {}".format(dirpath, filelist))
|
||||||
|
|
||||||
|
|
||||||
class PearsonTestCase(TestCase):
|
class PearsonTestCase(TestCase):
|
||||||
'''
|
'''
|
||||||
@@ -159,6 +164,7 @@ class PearsonTestCase(TestCase):
|
|||||||
# TestCenterUser.objects.all().delete()
|
# TestCenterUser.objects.all().delete()
|
||||||
# TestCenterRegistration.objects.all().delete()
|
# TestCenterRegistration.objects.all().delete()
|
||||||
|
|
||||||
|
|
||||||
class PearsonCommandTestCase(PearsonTestCase):
|
class PearsonCommandTestCase(PearsonTestCase):
|
||||||
|
|
||||||
def test_missing_demographic_fields(self):
|
def test_missing_demographic_fields(self):
|
||||||
@@ -199,8 +205,8 @@ class PearsonCommandTestCase(PearsonTestCase):
|
|||||||
|
|
||||||
def test_export_single_cdd(self):
|
def test_export_single_cdd(self):
|
||||||
# before we generate any tc_users, we expect there to be nothing to output:
|
# before we generate any tc_users, we expect there to be nothing to output:
|
||||||
options = { 'dest-from-settings' : True }
|
options = {'dest-from-settings': True}
|
||||||
with self.settings(PEARSON={ 'LOCAL_EXPORT' : self.export_dir }):
|
with self.settings(PEARSON={'LOCAL_EXPORT': self.export_dir}):
|
||||||
call_command('pearson_export_cdd', **options)
|
call_command('pearson_export_cdd', **options)
|
||||||
(filepath, numlines) = get_file_info(self.export_dir)
|
(filepath, numlines) = get_file_info(self.export_dir)
|
||||||
self.assertEquals(numlines, 1, "Expect cdd file to have no non-header lines")
|
self.assertEquals(numlines, 1, "Expect cdd file to have no non-header lines")
|
||||||
@@ -221,7 +227,7 @@ class PearsonCommandTestCase(PearsonTestCase):
|
|||||||
os.remove(filepath)
|
os.remove(filepath)
|
||||||
|
|
||||||
# if we modify the record, then it should be output again:
|
# if we modify the record, then it should be output again:
|
||||||
user_options = { 'first_name' : 'NewTestFirst', }
|
user_options = {'first_name': 'NewTestFirst', }
|
||||||
call_command('pearson_make_tc_user', username, **user_options)
|
call_command('pearson_make_tc_user', username, **user_options)
|
||||||
call_command('pearson_export_cdd', **options)
|
call_command('pearson_export_cdd', **options)
|
||||||
(filepath, numlines) = get_file_info(self.export_dir)
|
(filepath, numlines) = get_file_info(self.export_dir)
|
||||||
@@ -230,8 +236,8 @@ class PearsonCommandTestCase(PearsonTestCase):
|
|||||||
|
|
||||||
def test_export_single_ead(self):
|
def test_export_single_ead(self):
|
||||||
# before we generate any registrations, we expect there to be nothing to output:
|
# before we generate any registrations, we expect there to be nothing to output:
|
||||||
options = { 'dest-from-settings' : True }
|
options = {'dest-from-settings': True}
|
||||||
with self.settings(PEARSON={ 'LOCAL_EXPORT' : self.export_dir }):
|
with self.settings(PEARSON={'LOCAL_EXPORT': self.export_dir}):
|
||||||
call_command('pearson_export_ead', **options)
|
call_command('pearson_export_ead', **options)
|
||||||
(filepath, numlines) = get_file_info(self.export_dir)
|
(filepath, numlines) = get_file_info(self.export_dir)
|
||||||
self.assertEquals(numlines, 1, "Expect ead file to have no non-header lines")
|
self.assertEquals(numlines, 1, "Expect ead file to have no non-header lines")
|
||||||
@@ -261,8 +267,8 @@ class PearsonCommandTestCase(PearsonTestCase):
|
|||||||
|
|
||||||
def test_export_multiple(self):
|
def test_export_multiple(self):
|
||||||
create_multiple_registrations("export")
|
create_multiple_registrations("export")
|
||||||
with self.settings(PEARSON={ 'LOCAL_EXPORT' : self.export_dir }):
|
with self.settings(PEARSON={'LOCAL_EXPORT': self.export_dir}):
|
||||||
options = { 'dest-from-settings' : True }
|
options = {'dest-from-settings': True}
|
||||||
call_command('pearson_export_cdd', **options)
|
call_command('pearson_export_cdd', **options)
|
||||||
(filepath, numlines) = get_file_info(self.export_dir)
|
(filepath, numlines) = get_file_info(self.export_dir)
|
||||||
self.assertEquals(numlines, 5, "Expect cdd file to have four non-header lines: total was {}".format(numlines))
|
self.assertEquals(numlines, 5, "Expect cdd file to have four non-header lines: total was {}".format(numlines))
|
||||||
@@ -294,6 +300,7 @@ S3_BUCKET = 'edx-pearson-archive'
|
|||||||
AWS_ACCESS_KEY_ID = 'put yours here'
|
AWS_ACCESS_KEY_ID = 'put yours here'
|
||||||
AWS_SECRET_ACCESS_KEY = 'put yours here'
|
AWS_SECRET_ACCESS_KEY = 'put yours here'
|
||||||
|
|
||||||
|
|
||||||
class PearsonTransferTestCase(PearsonTestCase):
|
class PearsonTransferTestCase(PearsonTestCase):
|
||||||
'''
|
'''
|
||||||
Class for tests running Pearson transfers
|
Class for tests running Pearson transfers
|
||||||
@@ -302,14 +309,14 @@ class PearsonTransferTestCase(PearsonTestCase):
|
|||||||
def test_transfer_config(self):
|
def test_transfer_config(self):
|
||||||
with self.settings(DATADOG_API='FAKE_KEY'):
|
with self.settings(DATADOG_API='FAKE_KEY'):
|
||||||
# TODO: why is this failing with the wrong error message?!
|
# TODO: why is this failing with the wrong error message?!
|
||||||
stderrmsg = get_command_error_text('pearson_transfer', **{'mode' : 'garbage'})
|
stderrmsg = get_command_error_text('pearson_transfer', **{'mode': 'garbage'})
|
||||||
self.assertErrorContains(stderrmsg, 'Error: No PEARSON entries')
|
self.assertErrorContains(stderrmsg, 'Error: No PEARSON entries')
|
||||||
with self.settings(DATADOG_API='FAKE_KEY'):
|
with self.settings(DATADOG_API='FAKE_KEY'):
|
||||||
stderrmsg = get_command_error_text('pearson_transfer')
|
stderrmsg = get_command_error_text('pearson_transfer')
|
||||||
self.assertErrorContains(stderrmsg, 'Error: No PEARSON entries')
|
self.assertErrorContains(stderrmsg, 'Error: No PEARSON entries')
|
||||||
with self.settings(DATADOG_API='FAKE_KEY',
|
with self.settings(DATADOG_API='FAKE_KEY',
|
||||||
PEARSON={'LOCAL_EXPORT' : self.export_dir,
|
PEARSON={'LOCAL_EXPORT': self.export_dir,
|
||||||
'LOCAL_IMPORT' : self.import_dir }):
|
'LOCAL_IMPORT': self.import_dir}):
|
||||||
stderrmsg = get_command_error_text('pearson_transfer')
|
stderrmsg = get_command_error_text('pearson_transfer')
|
||||||
self.assertErrorContains(stderrmsg, 'Error: No entry in the PEARSON settings')
|
self.assertErrorContains(stderrmsg, 'Error: No entry in the PEARSON settings')
|
||||||
|
|
||||||
@@ -317,16 +324,16 @@ class PearsonTransferTestCase(PearsonTestCase):
|
|||||||
raise SkipTest()
|
raise SkipTest()
|
||||||
create_multiple_registrations('export_missing_dest')
|
create_multiple_registrations('export_missing_dest')
|
||||||
with self.settings(DATADOG_API='FAKE_KEY',
|
with self.settings(DATADOG_API='FAKE_KEY',
|
||||||
PEARSON={'LOCAL_EXPORT' : self.export_dir,
|
PEARSON={'LOCAL_EXPORT': self.export_dir,
|
||||||
'SFTP_EXPORT' : 'this/does/not/exist',
|
'SFTP_EXPORT': 'this/does/not/exist',
|
||||||
'SFTP_HOSTNAME' : SFTP_HOSTNAME,
|
'SFTP_HOSTNAME': SFTP_HOSTNAME,
|
||||||
'SFTP_USERNAME' : SFTP_USERNAME,
|
'SFTP_USERNAME': SFTP_USERNAME,
|
||||||
'SFTP_PASSWORD' : SFTP_PASSWORD,
|
'SFTP_PASSWORD': SFTP_PASSWORD,
|
||||||
'S3_BUCKET' : S3_BUCKET,
|
'S3_BUCKET': S3_BUCKET,
|
||||||
},
|
},
|
||||||
AWS_ACCESS_KEY_ID = AWS_ACCESS_KEY_ID,
|
AWS_ACCESS_KEY_ID=AWS_ACCESS_KEY_ID,
|
||||||
AWS_SECRET_ACCESS_KEY = AWS_SECRET_ACCESS_KEY):
|
AWS_SECRET_ACCESS_KEY=AWS_SECRET_ACCESS_KEY):
|
||||||
options = { 'mode' : 'export'}
|
options = {'mode': 'export'}
|
||||||
stderrmsg = get_command_error_text('pearson_transfer', **options)
|
stderrmsg = get_command_error_text('pearson_transfer', **options)
|
||||||
self.assertErrorContains(stderrmsg, 'Error: SFTP destination path does not exist')
|
self.assertErrorContains(stderrmsg, 'Error: SFTP destination path does not exist')
|
||||||
|
|
||||||
@@ -334,16 +341,16 @@ class PearsonTransferTestCase(PearsonTestCase):
|
|||||||
raise SkipTest()
|
raise SkipTest()
|
||||||
create_multiple_registrations("transfer_export")
|
create_multiple_registrations("transfer_export")
|
||||||
with self.settings(DATADOG_API='FAKE_KEY',
|
with self.settings(DATADOG_API='FAKE_KEY',
|
||||||
PEARSON={'LOCAL_EXPORT' : self.export_dir,
|
PEARSON={'LOCAL_EXPORT': self.export_dir,
|
||||||
'SFTP_EXPORT' : 'results/topvue',
|
'SFTP_EXPORT': 'results/topvue',
|
||||||
'SFTP_HOSTNAME' : SFTP_HOSTNAME,
|
'SFTP_HOSTNAME': SFTP_HOSTNAME,
|
||||||
'SFTP_USERNAME' : SFTP_USERNAME,
|
'SFTP_USERNAME': SFTP_USERNAME,
|
||||||
'SFTP_PASSWORD' : SFTP_PASSWORD,
|
'SFTP_PASSWORD': SFTP_PASSWORD,
|
||||||
'S3_BUCKET' : S3_BUCKET,
|
'S3_BUCKET': S3_BUCKET,
|
||||||
},
|
},
|
||||||
AWS_ACCESS_KEY_ID = AWS_ACCESS_KEY_ID,
|
AWS_ACCESS_KEY_ID=AWS_ACCESS_KEY_ID,
|
||||||
AWS_SECRET_ACCESS_KEY = AWS_SECRET_ACCESS_KEY):
|
AWS_SECRET_ACCESS_KEY=AWS_SECRET_ACCESS_KEY):
|
||||||
options = { 'mode' : 'export'}
|
options = {'mode': 'export'}
|
||||||
# call_command('pearson_transfer', **options)
|
# call_command('pearson_transfer', **options)
|
||||||
# # confirm that the export directory is still empty:
|
# # confirm that the export directory is still empty:
|
||||||
# self.assertEqual(len(os.listdir(self.export_dir)), 0, "expected export directory to be empty")
|
# self.assertEqual(len(os.listdir(self.export_dir)), 0, "expected export directory to be empty")
|
||||||
@@ -352,16 +359,16 @@ class PearsonTransferTestCase(PearsonTestCase):
|
|||||||
raise SkipTest()
|
raise SkipTest()
|
||||||
create_multiple_registrations('import_missing_src')
|
create_multiple_registrations('import_missing_src')
|
||||||
with self.settings(DATADOG_API='FAKE_KEY',
|
with self.settings(DATADOG_API='FAKE_KEY',
|
||||||
PEARSON={'LOCAL_IMPORT' : self.import_dir,
|
PEARSON={'LOCAL_IMPORT': self.import_dir,
|
||||||
'SFTP_IMPORT' : 'this/does/not/exist',
|
'SFTP_IMPORT': 'this/does/not/exist',
|
||||||
'SFTP_HOSTNAME' : SFTP_HOSTNAME,
|
'SFTP_HOSTNAME': SFTP_HOSTNAME,
|
||||||
'SFTP_USERNAME' : SFTP_USERNAME,
|
'SFTP_USERNAME': SFTP_USERNAME,
|
||||||
'SFTP_PASSWORD' : SFTP_PASSWORD,
|
'SFTP_PASSWORD': SFTP_PASSWORD,
|
||||||
'S3_BUCKET' : S3_BUCKET,
|
'S3_BUCKET': S3_BUCKET,
|
||||||
},
|
},
|
||||||
AWS_ACCESS_KEY_ID = AWS_ACCESS_KEY_ID,
|
AWS_ACCESS_KEY_ID=AWS_ACCESS_KEY_ID,
|
||||||
AWS_SECRET_ACCESS_KEY = AWS_SECRET_ACCESS_KEY):
|
AWS_SECRET_ACCESS_KEY=AWS_SECRET_ACCESS_KEY):
|
||||||
options = { 'mode' : 'import'}
|
options = {'mode': 'import'}
|
||||||
stderrmsg = get_command_error_text('pearson_transfer', **options)
|
stderrmsg = get_command_error_text('pearson_transfer', **options)
|
||||||
self.assertErrorContains(stderrmsg, 'Error: SFTP source path does not exist')
|
self.assertErrorContains(stderrmsg, 'Error: SFTP source path does not exist')
|
||||||
|
|
||||||
@@ -369,15 +376,15 @@ class PearsonTransferTestCase(PearsonTestCase):
|
|||||||
raise SkipTest()
|
raise SkipTest()
|
||||||
create_multiple_registrations('import_missing_src')
|
create_multiple_registrations('import_missing_src')
|
||||||
with self.settings(DATADOG_API='FAKE_KEY',
|
with self.settings(DATADOG_API='FAKE_KEY',
|
||||||
PEARSON={'LOCAL_IMPORT' : self.import_dir,
|
PEARSON={'LOCAL_IMPORT': self.import_dir,
|
||||||
'SFTP_IMPORT' : 'results',
|
'SFTP_IMPORT': 'results',
|
||||||
'SFTP_HOSTNAME' : SFTP_HOSTNAME,
|
'SFTP_HOSTNAME': SFTP_HOSTNAME,
|
||||||
'SFTP_USERNAME' : SFTP_USERNAME,
|
'SFTP_USERNAME': SFTP_USERNAME,
|
||||||
'SFTP_PASSWORD' : SFTP_PASSWORD,
|
'SFTP_PASSWORD': SFTP_PASSWORD,
|
||||||
'S3_BUCKET' : S3_BUCKET,
|
'S3_BUCKET': S3_BUCKET,
|
||||||
},
|
},
|
||||||
AWS_ACCESS_KEY_ID = AWS_ACCESS_KEY_ID,
|
AWS_ACCESS_KEY_ID=AWS_ACCESS_KEY_ID,
|
||||||
AWS_SECRET_ACCESS_KEY = AWS_SECRET_ACCESS_KEY):
|
AWS_SECRET_ACCESS_KEY=AWS_SECRET_ACCESS_KEY):
|
||||||
options = { 'mode' : 'import'}
|
options = {'mode': 'import'}
|
||||||
call_command('pearson_transfer', **options)
|
call_command('pearson_transfer', **options)
|
||||||
self.assertEqual(len(os.listdir(self.import_dir)), 0, "expected import directory to be empty")
|
self.assertEqual(len(os.listdir(self.import_dir)), 0, "expected import directory to be empty")
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ class UserProfile(models.Model):
|
|||||||
TEST_CENTER_STATUS_ACCEPTED = "Accepted"
|
TEST_CENTER_STATUS_ACCEPTED = "Accepted"
|
||||||
TEST_CENTER_STATUS_ERROR = "Error"
|
TEST_CENTER_STATUS_ERROR = "Error"
|
||||||
|
|
||||||
|
|
||||||
class TestCenterUser(models.Model):
|
class TestCenterUser(models.Model):
|
||||||
"""This is our representation of the User for in-person testing, and
|
"""This is our representation of the User for in-person testing, and
|
||||||
specifically for Pearson at this point. A few things to note:
|
specifically for Pearson at this point. A few things to note:
|
||||||
@@ -190,7 +191,7 @@ class TestCenterUser(models.Model):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def user_provided_fields():
|
def user_provided_fields():
|
||||||
return [ 'first_name', 'middle_name', 'last_name', 'suffix', 'salutation',
|
return ['first_name', 'middle_name', 'last_name', 'suffix', 'salutation',
|
||||||
'address_1', 'address_2', 'address_3', 'city', 'state', 'postal_code', 'country',
|
'address_1', 'address_2', 'address_3', 'city', 'state', 'postal_code', 'country',
|
||||||
'phone', 'extension', 'phone_country_code', 'fax', 'fax_country_code', 'company_name']
|
'phone', 'extension', 'phone_country_code', 'fax', 'fax_country_code', 'company_name']
|
||||||
|
|
||||||
@@ -208,7 +209,7 @@ class TestCenterUser(models.Model):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _generate_edx_id(prefix):
|
def _generate_edx_id(prefix):
|
||||||
NUM_DIGITS = 12
|
NUM_DIGITS = 12
|
||||||
return u"{}{:012}".format(prefix, randint(1, 10**NUM_DIGITS-1))
|
return u"{}{:012}".format(prefix, randint(1, 10 ** NUM_DIGITS - 1))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _generate_candidate_id():
|
def _generate_candidate_id():
|
||||||
@@ -237,10 +238,11 @@ class TestCenterUser(models.Model):
|
|||||||
def is_pending(self):
|
def is_pending(self):
|
||||||
return not self.is_accepted and not self.is_rejected
|
return not self.is_accepted and not self.is_rejected
|
||||||
|
|
||||||
|
|
||||||
class TestCenterUserForm(ModelForm):
|
class TestCenterUserForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = TestCenterUser
|
model = TestCenterUser
|
||||||
fields = ( 'first_name', 'middle_name', 'last_name', 'suffix', 'salutation',
|
fields = ('first_name', 'middle_name', 'last_name', 'suffix', 'salutation',
|
||||||
'address_1', 'address_2', 'address_3', 'city', 'state', 'postal_code', 'country',
|
'address_1', 'address_2', 'address_3', 'city', 'state', 'postal_code', 'country',
|
||||||
'phone', 'extension', 'phone_country_code', 'fax', 'fax_country_code', 'company_name')
|
'phone', 'extension', 'phone_country_code', 'fax', 'fax_country_code', 'company_name')
|
||||||
|
|
||||||
@@ -313,7 +315,8 @@ ACCOMMODATION_CODES = (
|
|||||||
('SRSGNR', 'Separate Room and Sign Language Interpreter'),
|
('SRSGNR', 'Separate Room and Sign Language Interpreter'),
|
||||||
)
|
)
|
||||||
|
|
||||||
ACCOMMODATION_CODE_DICT = { code : name for (code, name) in ACCOMMODATION_CODES }
|
ACCOMMODATION_CODE_DICT = {code: name for (code, name) in ACCOMMODATION_CODES}
|
||||||
|
|
||||||
|
|
||||||
class TestCenterRegistration(models.Model):
|
class TestCenterRegistration(models.Model):
|
||||||
"""
|
"""
|
||||||
@@ -417,7 +420,7 @@ class TestCenterRegistration(models.Model):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, testcenter_user, exam, accommodation_request):
|
def create(cls, testcenter_user, exam, accommodation_request):
|
||||||
registration = cls(testcenter_user = testcenter_user)
|
registration = cls(testcenter_user=testcenter_user)
|
||||||
registration.course_id = exam.course_id
|
registration.course_id = exam.course_id
|
||||||
registration.accommodation_request = accommodation_request.strip()
|
registration.accommodation_request = accommodation_request.strip()
|
||||||
registration.exam_series_code = exam.exam_series_code
|
registration.exam_series_code = exam.exam_series_code
|
||||||
@@ -501,7 +504,7 @@ class TestCenterRegistration(models.Model):
|
|||||||
return self.accommodation_code.split('*')
|
return self.accommodation_code.split('*')
|
||||||
|
|
||||||
def get_accommodation_names(self):
|
def get_accommodation_names(self):
|
||||||
return [ ACCOMMODATION_CODE_DICT.get(code, "Unknown code " + code) for code in self.get_accommodation_codes() ]
|
return [ACCOMMODATION_CODE_DICT.get(code, "Unknown code " + code) for code in self.get_accommodation_codes()]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def registration_signup_url(self):
|
def registration_signup_url(self):
|
||||||
@@ -537,7 +540,7 @@ class TestCenterRegistration(models.Model):
|
|||||||
class TestCenterRegistrationForm(ModelForm):
|
class TestCenterRegistrationForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = TestCenterRegistration
|
model = TestCenterRegistration
|
||||||
fields = ( 'accommodation_request', 'accommodation_code' )
|
fields = ('accommodation_request', 'accommodation_code')
|
||||||
|
|
||||||
def clean_accommodation_request(self):
|
def clean_accommodation_request(self):
|
||||||
code = self.cleaned_data['accommodation_request']
|
code = self.cleaned_data['accommodation_request']
|
||||||
@@ -576,6 +579,7 @@ def get_testcenter_registration(user, course_id, exam_series_code):
|
|||||||
# Correct this (https://nose.readthedocs.org/en/latest/finding_tests.html)
|
# Correct this (https://nose.readthedocs.org/en/latest/finding_tests.html)
|
||||||
get_testcenter_registration.__test__ = False
|
get_testcenter_registration.__test__ = False
|
||||||
|
|
||||||
|
|
||||||
def unique_id_for_user(user):
|
def unique_id_for_user(user):
|
||||||
"""
|
"""
|
||||||
Return a unique id for a user, suitable for inserting into
|
Return a unique id for a user, suitable for inserting into
|
||||||
@@ -666,6 +670,7 @@ class CourseEnrollmentAllowed(models.Model):
|
|||||||
|
|
||||||
#### Helper methods for use from python manage.py shell and other classes.
|
#### Helper methods for use from python manage.py shell and other classes.
|
||||||
|
|
||||||
|
|
||||||
def get_user_by_username_or_email(username_or_email):
|
def get_user_by_username_or_email(username_or_email):
|
||||||
"""
|
"""
|
||||||
Return a User object, looking up by email if username_or_email contains a
|
Return a User object, looking up by email if username_or_email contains a
|
||||||
@@ -767,4 +772,3 @@ def update_user_information(sender, instance, created, **kwargs):
|
|||||||
log = logging.getLogger("mitx.discussion")
|
log = logging.getLogger("mitx.discussion")
|
||||||
log.error(unicode(e))
|
log.error(unicode(e))
|
||||||
log.error("update user info to discussion failed for user with id: " + str(instance.id))
|
log.error("update user info to discussion failed for user with id: " + str(instance.id))
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ COURSE_2 = 'edx/full/6.002_Spring_2012'
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class CourseEndingTest(TestCase):
|
class CourseEndingTest(TestCase):
|
||||||
"""Test things related to course endings: certificates, surveys, etc"""
|
"""Test things related to course endings: certificates, surveys, etc"""
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ class CourseEndingTest(TestCase):
|
|||||||
{'status': 'processing',
|
{'status': 'processing',
|
||||||
'show_disabled_download_button': False,
|
'show_disabled_download_button': False,
|
||||||
'show_download_url': False,
|
'show_download_url': False,
|
||||||
'show_survey_button': False,})
|
'show_survey_button': False, })
|
||||||
|
|
||||||
cert_status = {'status': 'unavailable'}
|
cert_status = {'status': 'unavailable'}
|
||||||
self.assertEqual(_cert_info(user, course, cert_status),
|
self.assertEqual(_cert_info(user, course, cert_status),
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ from statsd import statsd
|
|||||||
log = logging.getLogger("mitx.student")
|
log = logging.getLogger("mitx.student")
|
||||||
Article = namedtuple('Article', 'title url author image deck publication publish_date')
|
Article = namedtuple('Article', 'title url author image deck publication publish_date')
|
||||||
|
|
||||||
|
|
||||||
def csrf_token(context):
|
def csrf_token(context):
|
||||||
''' A csrf token that can be included in a form.
|
''' A csrf token that can be included in a form.
|
||||||
'''
|
'''
|
||||||
@@ -74,7 +75,7 @@ def index(request, extra_context={}, user=None):
|
|||||||
|
|
||||||
# The course selection work is done in courseware.courses.
|
# The course selection work is done in courseware.courses.
|
||||||
domain = settings.MITX_FEATURES.get('FORCE_UNIVERSITY_DOMAIN') # normally False
|
domain = settings.MITX_FEATURES.get('FORCE_UNIVERSITY_DOMAIN') # normally False
|
||||||
if domain==False: # do explicit check, because domain=None is valid
|
if domain == False: # do explicit check, because domain=None is valid
|
||||||
domain = request.META.get('HTTP_HOST')
|
domain = request.META.get('HTTP_HOST')
|
||||||
|
|
||||||
courses = get_courses(None, domain=domain)
|
courses = get_courses(None, domain=domain)
|
||||||
@@ -97,6 +98,7 @@ import re
|
|||||||
day_pattern = re.compile('\s\d+,\s')
|
day_pattern = re.compile('\s\d+,\s')
|
||||||
multimonth_pattern = re.compile('\s?\-\s?\S+\s')
|
multimonth_pattern = re.compile('\s?\-\s?\S+\s')
|
||||||
|
|
||||||
|
|
||||||
def get_date_for_press(publish_date):
|
def get_date_for_press(publish_date):
|
||||||
import datetime
|
import datetime
|
||||||
# strip off extra months, and just use the first:
|
# strip off extra months, and just use the first:
|
||||||
@@ -107,6 +109,7 @@ def get_date_for_press(publish_date):
|
|||||||
date = datetime.datetime.strptime(date, "%B, %Y")
|
date = datetime.datetime.strptime(date, "%B, %Y")
|
||||||
return date
|
return date
|
||||||
|
|
||||||
|
|
||||||
def press(request):
|
def press(request):
|
||||||
json_articles = cache.get("student_press_json_articles")
|
json_articles = cache.get("student_press_json_articles")
|
||||||
if json_articles == None:
|
if json_articles == None:
|
||||||
@@ -148,6 +151,7 @@ def cert_info(user, course):
|
|||||||
|
|
||||||
return _cert_info(user, course, certificate_status_for_student(user, course.id))
|
return _cert_info(user, course, certificate_status_for_student(user, course.id))
|
||||||
|
|
||||||
|
|
||||||
def _cert_info(user, course, cert_status):
|
def _cert_info(user, course, cert_status):
|
||||||
"""
|
"""
|
||||||
Implements the logic for cert_info -- split out for testing.
|
Implements the logic for cert_info -- split out for testing.
|
||||||
@@ -175,7 +179,7 @@ def _cert_info(user, course, cert_status):
|
|||||||
|
|
||||||
d = {'status': status,
|
d = {'status': status,
|
||||||
'show_download_url': status == 'ready',
|
'show_download_url': status == 'ready',
|
||||||
'show_disabled_download_button': status == 'generating',}
|
'show_disabled_download_button': status == 'generating', }
|
||||||
|
|
||||||
if (status in ('generating', 'ready', 'notpassing', 'restricted') and
|
if (status in ('generating', 'ready', 'notpassing', 'restricted') and
|
||||||
course.end_of_course_survey_url is not None):
|
course.end_of_course_survey_url is not None):
|
||||||
@@ -204,6 +208,7 @@ def _cert_info(user, course, cert_status):
|
|||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
def dashboard(request):
|
def dashboard(request):
|
||||||
@@ -237,9 +242,9 @@ def dashboard(request):
|
|||||||
show_courseware_links_for = frozenset(course.id for course in courses
|
show_courseware_links_for = frozenset(course.id for course in courses
|
||||||
if has_access(request.user, course, 'load'))
|
if has_access(request.user, course, 'load'))
|
||||||
|
|
||||||
cert_statuses = { course.id: cert_info(request.user, course) for course in courses}
|
cert_statuses = {course.id: cert_info(request.user, course) for course in courses}
|
||||||
|
|
||||||
exam_registrations = { course.id: exam_registration_info(request.user, course) for course in courses}
|
exam_registrations = {course.id: exam_registration_info(request.user, course) for course in courses}
|
||||||
|
|
||||||
# Get the 3 most recent news
|
# Get the 3 most recent news
|
||||||
top_news = _get_news(top=3)
|
top_news = _get_news(top=3)
|
||||||
@@ -248,7 +253,7 @@ def dashboard(request):
|
|||||||
'message': message,
|
'message': message,
|
||||||
'staff_access': staff_access,
|
'staff_access': staff_access,
|
||||||
'errored_courses': errored_courses,
|
'errored_courses': errored_courses,
|
||||||
'show_courseware_links_for' : show_courseware_links_for,
|
'show_courseware_links_for': show_courseware_links_for,
|
||||||
'cert_statuses': cert_statuses,
|
'cert_statuses': cert_statuses,
|
||||||
'news': top_news,
|
'news': top_news,
|
||||||
'exam_registrations': exam_registrations,
|
'exam_registrations': exam_registrations,
|
||||||
@@ -312,7 +317,7 @@ def change_enrollment(request):
|
|||||||
'error': 'enrollment in {} not allowed at this time'
|
'error': 'enrollment in {} not allowed at this time'
|
||||||
.format(course.display_name)}
|
.format(course.display_name)}
|
||||||
|
|
||||||
org, course_num, run=course_id.split("/")
|
org, course_num, run = course_id.split("/")
|
||||||
statsd.increment("common.student.enrollment",
|
statsd.increment("common.student.enrollment",
|
||||||
tags=["org:{0}".format(org),
|
tags=["org:{0}".format(org),
|
||||||
"course:{0}".format(course_num),
|
"course:{0}".format(course_num),
|
||||||
@@ -326,7 +331,7 @@ def change_enrollment(request):
|
|||||||
enrollment = CourseEnrollment.objects.get(user=user, course_id=course_id)
|
enrollment = CourseEnrollment.objects.get(user=user, course_id=course_id)
|
||||||
enrollment.delete()
|
enrollment.delete()
|
||||||
|
|
||||||
org, course_num, run=course_id.split("/")
|
org, course_num, run = course_id.split("/")
|
||||||
statsd.increment("common.student.unenrollment",
|
statsd.increment("common.student.unenrollment",
|
||||||
tags=["org:{0}".format(org),
|
tags=["org:{0}".format(org),
|
||||||
"course:{0}".format(course_num),
|
"course:{0}".format(course_num),
|
||||||
@@ -345,7 +350,7 @@ def change_enrollment(request):
|
|||||||
def accounts_login(request, error=""):
|
def accounts_login(request, error=""):
|
||||||
|
|
||||||
|
|
||||||
return render_to_response('accounts_login.html', { 'error': error })
|
return render_to_response('accounts_login.html', {'error': error})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -424,6 +429,7 @@ def change_setting(request):
|
|||||||
return HttpResponse(json.dumps({'success': True,
|
return HttpResponse(json.dumps({'success': True,
|
||||||
'location': up.location, }))
|
'location': up.location, }))
|
||||||
|
|
||||||
|
|
||||||
def _do_create_account(post_vars):
|
def _do_create_account(post_vars):
|
||||||
"""
|
"""
|
||||||
Given cleaned post variables, create the User and UserProfile objects, as well as the
|
Given cleaned post variables, create the User and UserProfile objects, as well as the
|
||||||
@@ -551,7 +557,7 @@ def create_account(request, post_override=None):
|
|||||||
|
|
||||||
# Ok, looks like everything is legit. Create the account.
|
# Ok, looks like everything is legit. Create the account.
|
||||||
ret = _do_create_account(post_vars)
|
ret = _do_create_account(post_vars)
|
||||||
if isinstance(ret,HttpResponse): # if there was an error then return that
|
if isinstance(ret, HttpResponse): # if there was an error then return that
|
||||||
return ret
|
return ret
|
||||||
(user, profile, registration) = ret
|
(user, profile, registration) = ret
|
||||||
|
|
||||||
@@ -591,7 +597,7 @@ def create_account(request, post_override=None):
|
|||||||
eamap.user = login_user
|
eamap.user = login_user
|
||||||
eamap.dtsignup = datetime.datetime.now()
|
eamap.dtsignup = datetime.datetime.now()
|
||||||
eamap.save()
|
eamap.save()
|
||||||
log.debug('Updated ExternalAuthMap for %s to be %s' % (post_vars['username'],eamap))
|
log.debug('Updated ExternalAuthMap for %s to be %s' % (post_vars['username'], eamap))
|
||||||
|
|
||||||
if settings.MITX_FEATURES.get('BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'):
|
if settings.MITX_FEATURES.get('BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'):
|
||||||
log.debug('bypassing activation email')
|
log.debug('bypassing activation email')
|
||||||
@@ -603,6 +609,7 @@ def create_account(request, post_override=None):
|
|||||||
js = {'success': True}
|
js = {'success': True}
|
||||||
return HttpResponse(json.dumps(js), mimetype="application/json")
|
return HttpResponse(json.dumps(js), mimetype="application/json")
|
||||||
|
|
||||||
|
|
||||||
def exam_registration_info(user, course):
|
def exam_registration_info(user, course):
|
||||||
""" Returns a Registration object if the user is currently registered for a current
|
""" Returns a Registration object if the user is currently registered for a current
|
||||||
exam of the course. Returns None if the user is not registered, or if there is no
|
exam of the course. Returns None if the user is not registered, or if there is no
|
||||||
@@ -620,6 +627,7 @@ def exam_registration_info(user, course):
|
|||||||
registration = None
|
registration = None
|
||||||
return registration
|
return registration
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
def begin_exam_registration(request, course_id):
|
def begin_exam_registration(request, course_id):
|
||||||
@@ -663,6 +671,7 @@ def begin_exam_registration(request, course_id):
|
|||||||
|
|
||||||
return render_to_response('test_center_register.html', context)
|
return render_to_response('test_center_register.html', context)
|
||||||
|
|
||||||
|
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
def create_exam_registration(request, post_override=None):
|
def create_exam_registration(request, post_override=None):
|
||||||
'''
|
'''
|
||||||
@@ -725,7 +734,7 @@ def create_exam_registration(request, post_override=None):
|
|||||||
# this registration screen.
|
# this registration screen.
|
||||||
|
|
||||||
else:
|
else:
|
||||||
accommodation_request = post_vars.get('accommodation_request','')
|
accommodation_request = post_vars.get('accommodation_request', '')
|
||||||
registration = TestCenterRegistration.create(testcenter_user, exam, accommodation_request)
|
registration = TestCenterRegistration.create(testcenter_user, exam, accommodation_request)
|
||||||
needs_saving = True
|
needs_saving = True
|
||||||
log.info("User {0} enrolled in course {1} creating new exam registration".format(user.username, course_id))
|
log.info("User {0} enrolled in course {1} creating new exam registration".format(user.username, course_id))
|
||||||
@@ -834,16 +843,17 @@ def password_reset(request):
|
|||||||
|
|
||||||
form = PasswordResetForm(request.POST)
|
form = PasswordResetForm(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save(use_https = request.is_secure(),
|
form.save(use_https=request.is_secure(),
|
||||||
from_email = settings.DEFAULT_FROM_EMAIL,
|
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||||
request = request,
|
request=request,
|
||||||
domain_override = request.get_host())
|
domain_override=request.get_host())
|
||||||
return HttpResponse(json.dumps({'success':True,
|
return HttpResponse(json.dumps({'success': True,
|
||||||
'value': render_to_string('registration/password_reset_done.html', {})}))
|
'value': render_to_string('registration/password_reset_done.html', {})}))
|
||||||
else:
|
else:
|
||||||
return HttpResponse(json.dumps({'success': False,
|
return HttpResponse(json.dumps({'success': False,
|
||||||
'error': 'Invalid e-mail'}))
|
'error': 'Invalid e-mail'}))
|
||||||
|
|
||||||
|
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
def reactivation_email(request):
|
def reactivation_email(request):
|
||||||
''' Send an e-mail to reactivate a deactivated account, or to
|
''' Send an e-mail to reactivate a deactivated account, or to
|
||||||
@@ -856,6 +866,7 @@ def reactivation_email(request):
|
|||||||
'error': 'No inactive user with this e-mail exists'}))
|
'error': 'No inactive user with this e-mail exists'}))
|
||||||
return reactivation_email_for_user(user)
|
return reactivation_email_for_user(user)
|
||||||
|
|
||||||
|
|
||||||
def reactivation_email_for_user(user):
|
def reactivation_email_for_user(user):
|
||||||
reg = Registration.objects.get(user=user)
|
reg = Registration.objects.get(user=user)
|
||||||
|
|
||||||
@@ -996,11 +1007,11 @@ def pending_name_changes(request):
|
|||||||
|
|
||||||
changes = list(PendingNameChange.objects.all())
|
changes = list(PendingNameChange.objects.all())
|
||||||
js = {'students': [{'new_name': c.new_name,
|
js = {'students': [{'new_name': c.new_name,
|
||||||
'rationale':c.rationale,
|
'rationale': c.rationale,
|
||||||
'old_name':UserProfile.objects.get(user=c.user).name,
|
'old_name': UserProfile.objects.get(user=c.user).name,
|
||||||
'email':c.user.email,
|
'email': c.user.email,
|
||||||
'uid':c.user.id,
|
'uid': c.user.id,
|
||||||
'cid':c.id} for c in changes]}
|
'cid': c.id} for c in changes]}
|
||||||
return render_to_response('name_changes.html', js)
|
return render_to_response('name_changes.html', js)
|
||||||
|
|
||||||
|
|
||||||
@@ -1057,6 +1068,8 @@ def accept_name_change(request):
|
|||||||
|
|
||||||
# TODO: This is a giant kludge to give Pearson something to test against ASAP.
|
# TODO: This is a giant kludge to give Pearson something to test against ASAP.
|
||||||
# Will need to get replaced by something that actually ties into TestCenterUser
|
# Will need to get replaced by something that actually ties into TestCenterUser
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def test_center_login(request):
|
def test_center_login(request):
|
||||||
if not settings.MITX_FEATURES.get('ENABLE_PEARSON_HACK_TEST'):
|
if not settings.MITX_FEATURES.get('ENABLE_PEARSON_HACK_TEST'):
|
||||||
|
|||||||
@@ -2,21 +2,20 @@ from django.db import models
|
|||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
class TrackingLog(models.Model):
|
class TrackingLog(models.Model):
|
||||||
dtcreated = models.DateTimeField('creation date',auto_now_add=True)
|
dtcreated = models.DateTimeField('creation date', auto_now_add=True)
|
||||||
username = models.CharField(max_length=32,blank=True)
|
username = models.CharField(max_length=32, blank=True)
|
||||||
ip = models.CharField(max_length=32,blank=True)
|
ip = models.CharField(max_length=32, blank=True)
|
||||||
event_source = models.CharField(max_length=32)
|
event_source = models.CharField(max_length=32)
|
||||||
event_type = models.CharField(max_length=512,blank=True)
|
event_type = models.CharField(max_length=512, blank=True)
|
||||||
event = models.TextField(blank=True)
|
event = models.TextField(blank=True)
|
||||||
agent = models.CharField(max_length=256,blank=True)
|
agent = models.CharField(max_length=256, blank=True)
|
||||||
page = models.CharField(max_length=512,blank=True,null=True)
|
page = models.CharField(max_length=512, blank=True, null=True)
|
||||||
time = models.DateTimeField('event time')
|
time = models.DateTimeField('event time')
|
||||||
host = models.CharField(max_length=64,blank=True)
|
host = models.CharField(max_length=64, blank=True)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
s = "[%s] %s@%s: %s | %s | %s | %s" % (self.time, self.username, self.ip, self.event_source,
|
s = "[%s] %s@%s: %s | %s | %s | %s" % (self.time, self.username, self.ip, self.event_source,
|
||||||
self.event_type, self.page, self.event)
|
self.event_type, self.page, self.event)
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,19 +17,21 @@ from track.models import TrackingLog
|
|||||||
|
|
||||||
log = logging.getLogger("tracking")
|
log = logging.getLogger("tracking")
|
||||||
|
|
||||||
LOGFIELDS = ['username','ip','event_source','event_type','event','agent','page','time','host']
|
LOGFIELDS = ['username', 'ip', 'event_source', 'event_type', 'event', 'agent', 'page', 'time', 'host']
|
||||||
|
|
||||||
|
|
||||||
def log_event(event):
|
def log_event(event):
|
||||||
event_str = json.dumps(event)
|
event_str = json.dumps(event)
|
||||||
log.info(event_str[:settings.TRACK_MAX_EVENT])
|
log.info(event_str[:settings.TRACK_MAX_EVENT])
|
||||||
if settings.MITX_FEATURES.get('ENABLE_SQL_TRACKING_LOGS'):
|
if settings.MITX_FEATURES.get('ENABLE_SQL_TRACKING_LOGS'):
|
||||||
event['time'] = dateutil.parser.parse(event['time'])
|
event['time'] = dateutil.parser.parse(event['time'])
|
||||||
tldat = TrackingLog(**dict( (x,event[x]) for x in LOGFIELDS ))
|
tldat = TrackingLog(**dict((x, event[x]) for x in LOGFIELDS))
|
||||||
try:
|
try:
|
||||||
tldat.save()
|
tldat.save()
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
log.exception(err)
|
log.exception(err)
|
||||||
|
|
||||||
|
|
||||||
def user_track(request):
|
def user_track(request):
|
||||||
try: # TODO: Do the same for many of the optional META parameters
|
try: # TODO: Do the same for many of the optional META parameters
|
||||||
username = request.user.username
|
username = request.user.username
|
||||||
@@ -91,9 +93,10 @@ def server_track(request, event_type, event, page=None):
|
|||||||
return
|
return
|
||||||
log_event(event)
|
log_event(event)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
def view_tracking_log(request,args=''):
|
def view_tracking_log(request, args=''):
|
||||||
if not request.user.is_staff:
|
if not request.user.is_staff:
|
||||||
return redirect('/')
|
return redirect('/')
|
||||||
nlen = 100
|
nlen = 100
|
||||||
@@ -115,5 +118,4 @@ def view_tracking_log(request,args=''):
|
|||||||
for rinst in record_instances:
|
for rinst in record_instances:
|
||||||
rinst.dtstr = rinst.time.replace(tzinfo=pytz.utc).astimezone(pytz.timezone('US/Eastern')).strftime(fmt)
|
rinst.dtstr = rinst.time.replace(tzinfo=pytz.utc).astimezone(pytz.timezone('US/Eastern')).strftime(fmt)
|
||||||
|
|
||||||
return render_to_response('tracking_log.html',{'records':record_instances})
|
return render_to_response('tracking_log.html', {'records': record_instances})
|
||||||
|
|
||||||
|
|||||||
@@ -58,4 +58,3 @@ def cache_if_anonymous(view_func):
|
|||||||
return view_func(request, *args, **kwargs)
|
return view_func(request, *args, **kwargs)
|
||||||
|
|
||||||
return _decorated
|
return _decorated
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import time, datetime
|
import time
|
||||||
|
import datetime
|
||||||
import re
|
import re
|
||||||
import calendar
|
import calendar
|
||||||
|
|
||||||
|
|
||||||
def time_to_date(time_obj):
|
def time_to_date(time_obj):
|
||||||
"""
|
"""
|
||||||
Convert a time.time_struct to a true universal time (can pass to js Date constructor)
|
Convert a time.time_struct to a true universal time (can pass to js Date constructor)
|
||||||
@@ -9,6 +11,7 @@ def time_to_date(time_obj):
|
|||||||
# TODO change to using the isoformat() function on datetime. js date can parse those
|
# TODO change to using the isoformat() function on datetime. js date can parse those
|
||||||
return calendar.timegm(time_obj) * 1000
|
return calendar.timegm(time_obj) * 1000
|
||||||
|
|
||||||
|
|
||||||
def jsdate_to_time(field):
|
def jsdate_to_time(field):
|
||||||
"""
|
"""
|
||||||
Convert a universal time (iso format) or msec since epoch to a time obj
|
Convert a universal time (iso format) or msec since epoch to a time obj
|
||||||
@@ -16,7 +19,7 @@ def jsdate_to_time(field):
|
|||||||
if field is None:
|
if field is None:
|
||||||
return field
|
return field
|
||||||
elif isinstance(field, basestring): # iso format but ignores time zone assuming it's Z
|
elif isinstance(field, basestring): # iso format but ignores time zone assuming it's Z
|
||||||
d=datetime.datetime(*map(int, re.split('[^\d]', field)[:6])) # stop after seconds. Debatable
|
d = datetime.datetime(*map(int, re.split('[^\d]', field)[:6])) # stop after seconds. Debatable
|
||||||
return d.utctimetuple()
|
return d.utctimetuple()
|
||||||
elif isinstance(field, int) or isinstance(field, float):
|
elif isinstance(field, int) or isinstance(field, float):
|
||||||
return time.gmtime(field / 1000)
|
return time.gmtime(field / 1000)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ def expect_json(view_function):
|
|||||||
def expect_json_with_cloned_request(request, *args, **kwargs):
|
def expect_json_with_cloned_request(request, *args, **kwargs):
|
||||||
# cdodge: fix postback errors in CMS. The POST 'content-type' header can include additional information
|
# cdodge: fix postback errors in CMS. The POST 'content-type' header can include additional information
|
||||||
# e.g. 'charset', so we can't do a direct string compare
|
# e.g. 'charset', so we can't do a direct string compare
|
||||||
if request.META.get('CONTENT_TYPE','').lower().startswith("application/json"):
|
if request.META.get('CONTENT_TYPE', '').lower().startswith("application/json"):
|
||||||
cloned_request = copy.copy(request)
|
cloned_request = copy.copy(request)
|
||||||
cloned_request.POST = cloned_request.POST.copy()
|
cloned_request.POST = cloned_request.POST.copy()
|
||||||
cloned_request.POST.update(json.loads(request.body))
|
cloned_request.POST.update(json.loads(request.body))
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ def accepts(request, media_type):
|
|||||||
accept = parse_accept_header(request.META.get("HTTP_ACCEPT", ""))
|
accept = parse_accept_header(request.META.get("HTTP_ACCEPT", ""))
|
||||||
return media_type in [t for (t, p, q) in accept]
|
return media_type in [t for (t, p, q) in accept]
|
||||||
|
|
||||||
|
|
||||||
def debug_request(request):
|
def debug_request(request):
|
||||||
"""Return a pretty printed version of the request"""
|
"""Return a pretty printed version of the request"""
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from xmodule.vertical_module import VerticalModule
|
|||||||
|
|
||||||
log = logging.getLogger("mitx.xmodule_modifiers")
|
log = logging.getLogger("mitx.xmodule_modifiers")
|
||||||
|
|
||||||
|
|
||||||
def wrap_xmodule(get_html, module, template, context=None):
|
def wrap_xmodule(get_html, module, template, context=None):
|
||||||
"""
|
"""
|
||||||
Wraps the results of get_html in a standard <section> with identifying
|
Wraps the results of get_html in a standard <section> with identifying
|
||||||
@@ -32,7 +33,7 @@ def wrap_xmodule(get_html, module, template, context=None):
|
|||||||
def _get_html():
|
def _get_html():
|
||||||
context.update({
|
context.update({
|
||||||
'content': get_html(),
|
'content': get_html(),
|
||||||
'display_name' : module.metadata.get('display_name') if module.metadata is not None else None,
|
'display_name': module.metadata.get('display_name') if module.metadata is not None else None,
|
||||||
'class_': module.__class__.__name__,
|
'class_': module.__class__.__name__,
|
||||||
'module_name': module.js_module_name
|
'module_name': module.js_module_name
|
||||||
})
|
})
|
||||||
@@ -52,6 +53,7 @@ def replace_course_urls(get_html, course_id):
|
|||||||
return static_replace.replace_course_urls(get_html(), course_id)
|
return static_replace.replace_course_urls(get_html(), course_id)
|
||||||
return _get_html
|
return _get_html
|
||||||
|
|
||||||
|
|
||||||
def replace_static_urls(get_html, data_dir, course_namespace=None):
|
def replace_static_urls(get_html, data_dir, course_namespace=None):
|
||||||
"""
|
"""
|
||||||
Updates the supplied module with a new get_html function that wraps
|
Updates the supplied module with a new get_html function that wraps
|
||||||
@@ -64,6 +66,7 @@ def replace_static_urls(get_html, data_dir, course_namespace=None):
|
|||||||
return static_replace.replace_static_urls(get_html(), data_dir, course_namespace)
|
return static_replace.replace_static_urls(get_html(), data_dir, course_namespace)
|
||||||
return _get_html
|
return _get_html
|
||||||
|
|
||||||
|
|
||||||
def grade_histogram(module_id):
|
def grade_histogram(module_id):
|
||||||
''' Print out a histogram of grades on a given problem.
|
''' Print out a histogram of grades on a given problem.
|
||||||
Part of staff member debug info.
|
Part of staff member debug info.
|
||||||
@@ -114,35 +117,35 @@ def add_histogram(get_html, module, user):
|
|||||||
# doesn't like symlinks)
|
# doesn't like symlinks)
|
||||||
filepath = filename
|
filepath = filename
|
||||||
data_dir = osfs.root_path.rsplit('/')[-1]
|
data_dir = osfs.root_path.rsplit('/')[-1]
|
||||||
giturl = module.metadata.get('giturl','https://github.com/MITx')
|
giturl = module.metadata.get('giturl', 'https://github.com/MITx')
|
||||||
edit_link = "%s/%s/tree/master/%s" % (giturl,data_dir,filepath)
|
edit_link = "%s/%s/tree/master/%s" % (giturl, data_dir, filepath)
|
||||||
else:
|
else:
|
||||||
edit_link = False
|
edit_link = False
|
||||||
# Need to define all the variables that are about to be used
|
# Need to define all the variables that are about to be used
|
||||||
giturl = ""
|
giturl = ""
|
||||||
data_dir = ""
|
data_dir = ""
|
||||||
source_file = module.metadata.get('source_file','') # source used to generate the problem XML, eg latex or word
|
source_file = module.metadata.get('source_file', '') # source used to generate the problem XML, eg latex or word
|
||||||
|
|
||||||
# useful to indicate to staff if problem has been released or not
|
# useful to indicate to staff if problem has been released or not
|
||||||
# TODO (ichuang): use _has_access_descriptor.can_load in lms.courseware.access, instead of now>mstart comparison here
|
# TODO (ichuang): use _has_access_descriptor.can_load in lms.courseware.access, instead of now>mstart comparison here
|
||||||
now = time.gmtime()
|
now = time.gmtime()
|
||||||
is_released = "unknown"
|
is_released = "unknown"
|
||||||
mstart = getattr(module.descriptor,'start')
|
mstart = getattr(module.descriptor, 'start')
|
||||||
if mstart is not None:
|
if mstart is not None:
|
||||||
is_released = "<font color='red'>Yes!</font>" if (now > mstart) else "<font color='green'>Not yet</font>"
|
is_released = "<font color='red'>Yes!</font>" if (now > mstart) else "<font color='green'>Not yet</font>"
|
||||||
|
|
||||||
staff_context = {'definition': module.definition.get('data'),
|
staff_context = {'definition': module.definition.get('data'),
|
||||||
'metadata': json.dumps(module.metadata, indent=4),
|
'metadata': json.dumps(module.metadata, indent=4),
|
||||||
'location': module.location,
|
'location': module.location,
|
||||||
'xqa_key': module.metadata.get('xqa_key',''),
|
'xqa_key': module.metadata.get('xqa_key', ''),
|
||||||
'source_file' : source_file,
|
'source_file': source_file,
|
||||||
'source_url': '%s/%s/tree/master/%s' % (giturl,data_dir,source_file),
|
'source_url': '%s/%s/tree/master/%s' % (giturl, data_dir, source_file),
|
||||||
'category': str(module.__class__.__name__),
|
'category': str(module.__class__.__name__),
|
||||||
# Template uses element_id in js function names, so can't allow dashes
|
# Template uses element_id in js function names, so can't allow dashes
|
||||||
'element_id': module.location.html_id().replace('-','_'),
|
'element_id': module.location.html_id().replace('-', '_'),
|
||||||
'edit_link': edit_link,
|
'edit_link': edit_link,
|
||||||
'user': user,
|
'user': user,
|
||||||
'xqa_server' : settings.MITX_FEATURES.get('USE_XQA_SERVER','http://xqa:server@content-qa.mitx.mit.edu/xqa'),
|
'xqa_server': settings.MITX_FEATURES.get('USE_XQA_SERVER', 'http://xqa:server@content-qa.mitx.mit.edu/xqa'),
|
||||||
'histogram': json.dumps(histogram),
|
'histogram': json.dumps(histogram),
|
||||||
'render_histogram': render_histogram,
|
'render_histogram': render_histogram,
|
||||||
'module_content': get_html(),
|
'module_content': get_html(),
|
||||||
@@ -151,4 +154,3 @@ def add_histogram(get_html, module, user):
|
|||||||
return render_to_string("staff_problem_info.html", staff_context)
|
return render_to_string("staff_problem_info.html", staff_context)
|
||||||
|
|
||||||
return _get_html
|
return _get_html
|
||||||
|
|
||||||
|
|||||||
@@ -121,9 +121,9 @@ def evaluator(variables, functions, string, cs=False):
|
|||||||
# confusing. They may also conflict with variables if we ever allow e.g.
|
# confusing. They may also conflict with variables if we ever allow e.g.
|
||||||
# 5R instead of 5*R
|
# 5R instead of 5*R
|
||||||
suffixes = {'%': 0.01, 'k': 1e3, 'M': 1e6, 'G': 1e9,
|
suffixes = {'%': 0.01, 'k': 1e3, 'M': 1e6, 'G': 1e9,
|
||||||
'T': 1e12,# 'P':1e15,'E':1e18,'Z':1e21,'Y':1e24,
|
'T': 1e12, # 'P':1e15,'E':1e18,'Z':1e21,'Y':1e24,
|
||||||
'c': 1e-2, 'm': 1e-3, 'u': 1e-6,
|
'c': 1e-2, 'm': 1e-3, 'u': 1e-6,
|
||||||
'n': 1e-9, 'p': 1e-12}# ,'f':1e-15,'a':1e-18,'z':1e-21,'y':1e-24}
|
'n': 1e-9, 'p': 1e-12} # ,'f':1e-15,'a':1e-18,'z':1e-21,'y':1e-24}
|
||||||
|
|
||||||
def super_float(text):
|
def super_float(text):
|
||||||
''' Like float, but with si extensions. 1k goes to 1000'''
|
''' Like float, but with si extensions. 1k goes to 1000'''
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ global_context = {'random': random,
|
|||||||
'draganddrop': verifiers.draganddrop}
|
'draganddrop': verifiers.draganddrop}
|
||||||
|
|
||||||
# These should be removed from HTML output, including all subelements
|
# These should be removed from HTML output, including all subelements
|
||||||
html_problem_semantics = ["codeparam", "responseparam", "answer", "script", "hintgroup", "openendedparam","openendedrubric"]
|
html_problem_semantics = ["codeparam", "responseparam", "answer", "script", "hintgroup", "openendedparam", "openendedrubric"]
|
||||||
|
|
||||||
log = logging.getLogger('mitx.' + __name__)
|
log = logging.getLogger('mitx.' + __name__)
|
||||||
|
|
||||||
@@ -453,7 +453,7 @@ class LoncapaProblem(object):
|
|||||||
exec code in context, context
|
exec code in context, context
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
log.exception("Error while execing script code: " + code)
|
log.exception("Error while execing script code: " + code)
|
||||||
msg = "Error while executing script code: %s" % str(err).replace('<','<')
|
msg = "Error while executing script code: %s" % str(err).replace('<', '<')
|
||||||
raise responsetypes.LoncapaProblemError(msg)
|
raise responsetypes.LoncapaProblemError(msg)
|
||||||
finally:
|
finally:
|
||||||
sys.path = original_path
|
sys.path = original_path
|
||||||
@@ -502,7 +502,7 @@ class LoncapaProblem(object):
|
|||||||
'id': problemtree.get('id'),
|
'id': problemtree.get('id'),
|
||||||
'feedback': {'message': msg,
|
'feedback': {'message': msg,
|
||||||
'hint': hint,
|
'hint': hint,
|
||||||
'hintmode': hintmode,}}
|
'hintmode': hintmode, }}
|
||||||
|
|
||||||
input_type_cls = inputtypes.registry.get_class_for_tag(problemtree.tag)
|
input_type_cls = inputtypes.registry.get_class_for_tag(problemtree.tag)
|
||||||
the_input = input_type_cls(self.system, problemtree, state)
|
the_input = input_type_cls(self.system, problemtree, state)
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -17,17 +17,17 @@ from nltk.tree import Tree
|
|||||||
ARROWS = ('<->', '->')
|
ARROWS = ('<->', '->')
|
||||||
|
|
||||||
## Defines a simple pyparsing tokenizer for chemical equations
|
## Defines a simple pyparsing tokenizer for chemical equations
|
||||||
elements = ['Ac','Ag','Al','Am','Ar','As','At','Au','B','Ba','Be',
|
elements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be',
|
||||||
'Bh','Bi','Bk','Br','C','Ca','Cd','Ce','Cf','Cl','Cm',
|
'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm',
|
||||||
'Cn','Co','Cr','Cs','Cu','Db','Ds','Dy','Er','Es','Eu',
|
'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu',
|
||||||
'F','Fe','Fl','Fm','Fr','Ga','Gd','Ge','H','He','Hf',
|
'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf',
|
||||||
'Hg','Ho','Hs','I','In','Ir','K','Kr','La','Li','Lr',
|
'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr',
|
||||||
'Lu','Lv','Md','Mg','Mn','Mo','Mt','N','Na','Nb','Nd',
|
'Lu', 'Lv', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd',
|
||||||
'Ne','Ni','No','Np','O','Os','P','Pa','Pb','Pd','Pm',
|
'Ne', 'Ni', 'No', 'Np', 'O', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm',
|
||||||
'Po','Pr','Pt','Pu','Ra','Rb','Re','Rf','Rg','Rh','Rn',
|
'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn',
|
||||||
'Ru','S','Sb','Sc','Se','Sg','Si','Sm','Sn','Sr','Ta',
|
'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta',
|
||||||
'Tb','Tc','Te','Th','Ti','Tl','Tm','U','Uuo','Uup',
|
'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'U', 'Uuo', 'Uup',
|
||||||
'Uus','Uut','V','W','Xe','Y','Yb','Zn','Zr']
|
'Uus', 'Uut', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr']
|
||||||
digits = map(str, range(10))
|
digits = map(str, range(10))
|
||||||
symbols = list("[](){}^+-/")
|
symbols = list("[](){}^+-/")
|
||||||
phases = ["(s)", "(l)", "(g)", "(aq)"]
|
phases = ["(s)", "(l)", "(g)", "(aq)"]
|
||||||
@@ -252,7 +252,7 @@ def _get_final_tree(s):
|
|||||||
'''
|
'''
|
||||||
tokenized = tokenizer.parseString(s)
|
tokenized = tokenizer.parseString(s)
|
||||||
parsed = parser.parse(tokenized)
|
parsed = parser.parse(tokenized)
|
||||||
merged = _merge_children(parsed, {'S','group'})
|
merged = _merge_children(parsed, {'S', 'group'})
|
||||||
final = _clean_parse_tree(merged)
|
final = _clean_parse_tree(merged)
|
||||||
return final
|
return final
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#
|
#
|
||||||
# Used by responsetypes and capa_problem
|
# Used by responsetypes and capa_problem
|
||||||
|
|
||||||
|
|
||||||
class CorrectMap(object):
|
class CorrectMap(object):
|
||||||
"""
|
"""
|
||||||
Stores map between answer_id and response evaluation result for each question
|
Stores map between answer_id and response evaluation result for each question
|
||||||
@@ -152,6 +153,3 @@ class CorrectMap(object):
|
|||||||
if not isinstance(other_cmap, CorrectMap):
|
if not isinstance(other_cmap, CorrectMap):
|
||||||
raise Exception('CorrectMap.update called with invalid argument %s' % other_cmap)
|
raise Exception('CorrectMap.update called with invalid argument %s' % other_cmap)
|
||||||
self.cmap.update(other_cmap.get_dict())
|
self.cmap.update(other_cmap.get_dict())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ log = logging.getLogger('mitx.' + __name__)
|
|||||||
registry = TagRegistry()
|
registry = TagRegistry()
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class MathRenderer(object):
|
class MathRenderer(object):
|
||||||
tags = ['math']
|
tags = ['math']
|
||||||
|
|
||||||
@@ -77,6 +79,7 @@ registry.register(MathRenderer)
|
|||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class SolutionRenderer(object):
|
class SolutionRenderer(object):
|
||||||
'''
|
'''
|
||||||
A solution is just a <span>...</span> which is given an ID, that is used for displaying an
|
A solution is just a <span>...</span> which is given an ID, that is used for displaying an
|
||||||
@@ -97,4 +100,3 @@ class SolutionRenderer(object):
|
|||||||
return etree.XML(html)
|
return etree.XML(html)
|
||||||
|
|
||||||
registry.register(SolutionRenderer)
|
registry.register(SolutionRenderer)
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ log = logging.getLogger('mitx.' + __name__)
|
|||||||
|
|
||||||
registry = TagRegistry()
|
registry = TagRegistry()
|
||||||
|
|
||||||
|
|
||||||
class Attribute(object):
|
class Attribute(object):
|
||||||
"""
|
"""
|
||||||
Allows specifying required and optional attributes for input types.
|
Allows specifying required and optional attributes for input types.
|
||||||
@@ -413,7 +414,7 @@ class JavascriptInput(InputTypeBase):
|
|||||||
return [Attribute('params', None),
|
return [Attribute('params', None),
|
||||||
Attribute('problem_state', None),
|
Attribute('problem_state', None),
|
||||||
Attribute('display_class', None),
|
Attribute('display_class', None),
|
||||||
Attribute('display_file', None),]
|
Attribute('display_file', None), ]
|
||||||
|
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
@@ -477,12 +478,13 @@ class TextLine(InputTypeBase):
|
|||||||
|
|
||||||
def _extra_context(self):
|
def _extra_context(self):
|
||||||
return {'do_math': self.do_math,
|
return {'do_math': self.do_math,
|
||||||
'preprocessor': self.preprocessor,}
|
'preprocessor': self.preprocessor, }
|
||||||
|
|
||||||
registry.register(TextLine)
|
registry.register(TextLine)
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class FileSubmission(InputTypeBase):
|
class FileSubmission(InputTypeBase):
|
||||||
"""
|
"""
|
||||||
Upload some files (e.g. for programming assignments)
|
Upload some files (e.g. for programming assignments)
|
||||||
@@ -508,7 +510,7 @@ class FileSubmission(InputTypeBase):
|
|||||||
Convert the list of allowed files to a convenient format.
|
Convert the list of allowed files to a convenient format.
|
||||||
"""
|
"""
|
||||||
return [Attribute('allowed_files', '[]', transform=cls.parse_files),
|
return [Attribute('allowed_files', '[]', transform=cls.parse_files),
|
||||||
Attribute('required_files', '[]', transform=cls.parse_files),]
|
Attribute('required_files', '[]', transform=cls.parse_files), ]
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
"""
|
"""
|
||||||
@@ -524,7 +526,7 @@ class FileSubmission(InputTypeBase):
|
|||||||
self.msg = FileSubmission.submitted_msg
|
self.msg = FileSubmission.submitted_msg
|
||||||
|
|
||||||
def _extra_context(self):
|
def _extra_context(self):
|
||||||
return {'queue_len': self.queue_len,}
|
return {'queue_len': self.queue_len, }
|
||||||
return context
|
return context
|
||||||
|
|
||||||
registry.register(FileSubmission)
|
registry.register(FileSubmission)
|
||||||
@@ -582,7 +584,7 @@ class CodeInput(InputTypeBase):
|
|||||||
|
|
||||||
def _extra_context(self):
|
def _extra_context(self):
|
||||||
"""Defined queue_len, add it """
|
"""Defined queue_len, add it """
|
||||||
return {'queue_len': self.queue_len,}
|
return {'queue_len': self.queue_len, }
|
||||||
|
|
||||||
registry.register(CodeInput)
|
registry.register(CodeInput)
|
||||||
|
|
||||||
@@ -606,7 +608,7 @@ class Schematic(InputTypeBase):
|
|||||||
Attribute('parts', None),
|
Attribute('parts', None),
|
||||||
Attribute('analyses', None),
|
Attribute('analyses', None),
|
||||||
Attribute('initial_value', None),
|
Attribute('initial_value', None),
|
||||||
Attribute('submit_analyses', None),]
|
Attribute('submit_analyses', None), ]
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@@ -614,6 +616,7 @@ registry.register(Schematic)
|
|||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class ImageInput(InputTypeBase):
|
class ImageInput(InputTypeBase):
|
||||||
"""
|
"""
|
||||||
Clickable image as an input field. Element should specify the image source, height,
|
Clickable image as an input field. Element should specify the image source, height,
|
||||||
@@ -635,7 +638,7 @@ class ImageInput(InputTypeBase):
|
|||||||
"""
|
"""
|
||||||
return [Attribute('src'),
|
return [Attribute('src'),
|
||||||
Attribute('height'),
|
Attribute('height'),
|
||||||
Attribute('width'),]
|
Attribute('width'), ]
|
||||||
|
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
@@ -660,6 +663,7 @@ registry.register(ImageInput)
|
|||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class Crystallography(InputTypeBase):
|
class Crystallography(InputTypeBase):
|
||||||
"""
|
"""
|
||||||
An input for crystallography -- user selects 3 points on the axes, and we get a plane.
|
An input for crystallography -- user selects 3 points on the axes, and we get a plane.
|
||||||
@@ -728,18 +732,19 @@ class ChemicalEquationInput(InputTypeBase):
|
|||||||
"""
|
"""
|
||||||
Can set size of text field.
|
Can set size of text field.
|
||||||
"""
|
"""
|
||||||
return [Attribute('size', '20'),]
|
return [Attribute('size', '20'), ]
|
||||||
|
|
||||||
def _extra_context(self):
|
def _extra_context(self):
|
||||||
"""
|
"""
|
||||||
TODO (vshnayder): Get rid of this once we have a standard way of requiring js to be loaded.
|
TODO (vshnayder): Get rid of this once we have a standard way of requiring js to be loaded.
|
||||||
"""
|
"""
|
||||||
return {'previewer': '/static/js/capa/chemical_equation_preview.js',}
|
return {'previewer': '/static/js/capa/chemical_equation_preview.js', }
|
||||||
|
|
||||||
registry.register(ChemicalEquationInput)
|
registry.register(ChemicalEquationInput)
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class DragAndDropInput(InputTypeBase):
|
class DragAndDropInput(InputTypeBase):
|
||||||
"""
|
"""
|
||||||
Input for drag and drop problems. Allows student to drag and drop images and
|
Input for drag and drop problems. Allows student to drag and drop images and
|
||||||
|
|||||||
@@ -186,8 +186,8 @@ class LoncapaResponse(object):
|
|||||||
tree = etree.Element('span')
|
tree = etree.Element('span')
|
||||||
|
|
||||||
# problem author can make this span display:inline
|
# problem author can make this span display:inline
|
||||||
if self.xml.get('inline',''):
|
if self.xml.get('inline', ''):
|
||||||
tree.set('class','inline')
|
tree.set('class', 'inline')
|
||||||
|
|
||||||
for item in self.xml:
|
for item in self.xml:
|
||||||
# call provided procedure to do the rendering
|
# call provided procedure to do the rendering
|
||||||
@@ -1294,7 +1294,7 @@ class CodeResponse(LoncapaResponse):
|
|||||||
|
|
||||||
# State associated with the queueing request
|
# State associated with the queueing request
|
||||||
queuestate = {'key': queuekey,
|
queuestate = {'key': queuekey,
|
||||||
'time': qtime,}
|
'time': qtime, }
|
||||||
|
|
||||||
cmap = CorrectMap()
|
cmap = CorrectMap()
|
||||||
if error:
|
if error:
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import xml.sax.saxutils as saxutils
|
|||||||
|
|
||||||
TEST_DIR = os.path.dirname(os.path.realpath(__file__))
|
TEST_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
|
|
||||||
def tst_render_template(template, context):
|
def tst_render_template(template, context):
|
||||||
"""
|
"""
|
||||||
A test version of render to template. Renders to the repr of the context, completely ignoring
|
A test version of render to template. Renders to the repr of the context, completely ignoring
|
||||||
@@ -25,7 +26,7 @@ test_system = Mock(
|
|||||||
user=Mock(),
|
user=Mock(),
|
||||||
filestore=fs.osfs.OSFS(os.path.join(TEST_DIR, "test_files")),
|
filestore=fs.osfs.OSFS(os.path.join(TEST_DIR, "test_files")),
|
||||||
debug=True,
|
debug=True,
|
||||||
xqueue={'interface':None, 'callback_url':'/', 'default_queuename': 'testqueue', 'waittime': 10},
|
xqueue={'interface': None, 'callback_url': '/', 'default_queuename': 'testqueue', 'waittime': 10},
|
||||||
node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"),
|
node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"),
|
||||||
anonymous_student_id = 'student'
|
anonymous_student_id='student'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from capa import customrender
|
|||||||
# just a handy shortcut
|
# just a handy shortcut
|
||||||
lookup_tag = customrender.registry.get_class_for_tag
|
lookup_tag = customrender.registry.get_class_for_tag
|
||||||
|
|
||||||
|
|
||||||
def extract_context(xml):
|
def extract_context(xml):
|
||||||
"""
|
"""
|
||||||
Given an xml element corresponding to the output of test_system.render_template, get back the
|
Given an xml element corresponding to the output of test_system.render_template, get back the
|
||||||
@@ -15,9 +16,11 @@ def extract_context(xml):
|
|||||||
"""
|
"""
|
||||||
return eval(xml.text)
|
return eval(xml.text)
|
||||||
|
|
||||||
|
|
||||||
def quote_attr(s):
|
def quote_attr(s):
|
||||||
return saxutils.quoteattr(s)[1:-1] # don't want the outer quotes
|
return saxutils.quoteattr(s)[1:-1] # don't want the outer quotes
|
||||||
|
|
||||||
|
|
||||||
class HelperTest(unittest.TestCase):
|
class HelperTest(unittest.TestCase):
|
||||||
'''
|
'''
|
||||||
Make sure that our helper function works!
|
Make sure that our helper function works!
|
||||||
@@ -50,7 +53,7 @@ class SolutionRenderTest(unittest.TestCase):
|
|||||||
# our test_system "renders" templates to a div with the repr of the context
|
# our test_system "renders" templates to a div with the repr of the context
|
||||||
xml = renderer.get_html()
|
xml = renderer.get_html()
|
||||||
context = extract_context(xml)
|
context = extract_context(xml)
|
||||||
self.assertEqual(context, {'id' : 'solution_12'})
|
self.assertEqual(context, {'id': 'solution_12'})
|
||||||
|
|
||||||
|
|
||||||
class MathRenderTest(unittest.TestCase):
|
class MathRenderTest(unittest.TestCase):
|
||||||
@@ -73,4 +76,3 @@ class MathRenderTest(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
# NOTE: not testing get_html yet because I don't understand why it's doing what it's doing.
|
# NOTE: not testing get_html yet because I don't understand why it's doing what it's doing.
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ lookup_tag = inputtypes.registry.get_class_for_tag
|
|||||||
def quote_attr(s):
|
def quote_attr(s):
|
||||||
return saxutils.quoteattr(s)[1:-1] # don't want the outer quotes
|
return saxutils.quoteattr(s)[1:-1] # don't want the outer quotes
|
||||||
|
|
||||||
|
|
||||||
class OptionInputTest(unittest.TestCase):
|
class OptionInputTest(unittest.TestCase):
|
||||||
'''
|
'''
|
||||||
Make sure option inputs work
|
Make sure option inputs work
|
||||||
@@ -100,7 +101,7 @@ class ChoiceGroupTest(unittest.TestCase):
|
|||||||
'input_type': expected_input_type,
|
'input_type': expected_input_type,
|
||||||
'choices': [('foil1', '<text>This is foil One.</text>'),
|
'choices': [('foil1', '<text>This is foil One.</text>'),
|
||||||
('foil2', '<text>This is foil Two.</text>'),
|
('foil2', '<text>This is foil Two.</text>'),
|
||||||
('foil3', 'This is foil Three.'),],
|
('foil3', 'This is foil Three.'), ],
|
||||||
'name_array_suffix': expected_suffix, # what is this for??
|
'name_array_suffix': expected_suffix, # what is this for??
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +138,7 @@ class JavascriptInputTest(unittest.TestCase):
|
|||||||
|
|
||||||
element = etree.fromstring(xml_str)
|
element = etree.fromstring(xml_str)
|
||||||
|
|
||||||
state = {'value': '3',}
|
state = {'value': '3', }
|
||||||
the_input = lookup_tag('javascriptinput')(test_system, element, state)
|
the_input = lookup_tag('javascriptinput')(test_system, element, state)
|
||||||
|
|
||||||
context = the_input._get_render_context()
|
context = the_input._get_render_context()
|
||||||
@@ -149,7 +150,7 @@ class JavascriptInputTest(unittest.TestCase):
|
|||||||
'params': params,
|
'params': params,
|
||||||
'display_file': display_file,
|
'display_file': display_file,
|
||||||
'display_class': display_class,
|
'display_class': display_class,
|
||||||
'problem_state': problem_state,}
|
'problem_state': problem_state, }
|
||||||
|
|
||||||
self.assertEqual(context, expected)
|
self.assertEqual(context, expected)
|
||||||
|
|
||||||
@@ -165,7 +166,7 @@ class TextLineTest(unittest.TestCase):
|
|||||||
|
|
||||||
element = etree.fromstring(xml_str)
|
element = etree.fromstring(xml_str)
|
||||||
|
|
||||||
state = {'value': 'BumbleBee',}
|
state = {'value': 'BumbleBee', }
|
||||||
the_input = lookup_tag('textline')(test_system, element, state)
|
the_input = lookup_tag('textline')(test_system, element, state)
|
||||||
|
|
||||||
context = the_input._get_render_context()
|
context = the_input._get_render_context()
|
||||||
@@ -193,7 +194,7 @@ class TextLineTest(unittest.TestCase):
|
|||||||
|
|
||||||
element = etree.fromstring(xml_str)
|
element = etree.fromstring(xml_str)
|
||||||
|
|
||||||
state = {'value': 'BumbleBee',}
|
state = {'value': 'BumbleBee', }
|
||||||
the_input = lookup_tag('textline')(test_system, element, state)
|
the_input = lookup_tag('textline')(test_system, element, state)
|
||||||
|
|
||||||
context = the_input._get_render_context()
|
context = the_input._get_render_context()
|
||||||
@@ -231,7 +232,7 @@ class FileSubmissionTest(unittest.TestCase):
|
|||||||
|
|
||||||
state = {'value': 'BumbleBee.py',
|
state = {'value': 'BumbleBee.py',
|
||||||
'status': 'incomplete',
|
'status': 'incomplete',
|
||||||
'feedback' : {'message': '3'}, }
|
'feedback': {'message': '3'}, }
|
||||||
input_class = lookup_tag('filesubmission')
|
input_class = lookup_tag('filesubmission')
|
||||||
the_input = input_class(test_system, element, state)
|
the_input = input_class(test_system, element, state)
|
||||||
|
|
||||||
@@ -275,7 +276,7 @@ class CodeInputTest(unittest.TestCase):
|
|||||||
|
|
||||||
state = {'value': 'print "good evening"',
|
state = {'value': 'print "good evening"',
|
||||||
'status': 'incomplete',
|
'status': 'incomplete',
|
||||||
'feedback' : {'message': '3'}, }
|
'feedback': {'message': '3'}, }
|
||||||
|
|
||||||
input_class = lookup_tag('codeinput')
|
input_class = lookup_tag('codeinput')
|
||||||
the_input = input_class(test_system, element, state)
|
the_input = input_class(test_system, element, state)
|
||||||
@@ -488,7 +489,7 @@ class ChemicalEquationTest(unittest.TestCase):
|
|||||||
|
|
||||||
element = etree.fromstring(xml_str)
|
element = etree.fromstring(xml_str)
|
||||||
|
|
||||||
state = {'value': 'H2OYeah',}
|
state = {'value': 'H2OYeah', }
|
||||||
the_input = lookup_tag('chemicalequationinput')(test_system, element, state)
|
the_input = lookup_tag('chemicalequationinput')(test_system, element, state)
|
||||||
|
|
||||||
context = the_input._get_render_context()
|
context = the_input._get_render_context()
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from capa.correctmap import CorrectMap
|
|||||||
from capa.util import convert_files_to_filenames
|
from capa.util import convert_files_to_filenames
|
||||||
from capa.xqueue_interface import dateformat
|
from capa.xqueue_interface import dateformat
|
||||||
|
|
||||||
|
|
||||||
class MultiChoiceTest(unittest.TestCase):
|
class MultiChoiceTest(unittest.TestCase):
|
||||||
def test_MC_grade(self):
|
def test_MC_grade(self):
|
||||||
multichoice_file = os.path.dirname(__file__) + "/test_files/multichoice.xml"
|
multichoice_file = os.path.dirname(__file__) + "/test_files/multichoice.xml"
|
||||||
@@ -295,16 +296,16 @@ class CodeResponseTest(unittest.TestCase):
|
|||||||
old_cmap = CorrectMap()
|
old_cmap = CorrectMap()
|
||||||
for i, answer_id in enumerate(answer_ids):
|
for i, answer_id in enumerate(answer_ids):
|
||||||
queuekey = 1000 + i
|
queuekey = 1000 + i
|
||||||
queuestate = CodeResponseTest.make_queuestate(1000+i, datetime.now())
|
queuestate = CodeResponseTest.make_queuestate(1000 + i, datetime.now())
|
||||||
old_cmap.update(CorrectMap(answer_id=answer_ids[i], queuestate=queuestate))
|
old_cmap.update(CorrectMap(answer_id=answer_ids[i], queuestate=queuestate))
|
||||||
|
|
||||||
# Message format common to external graders
|
# Message format common to external graders
|
||||||
grader_msg = '<span>MESSAGE</span>' # Must be valid XML
|
grader_msg = '<span>MESSAGE</span>' # Must be valid XML
|
||||||
correct_score_msg = json.dumps({'correct':True, 'score':1, 'msg': grader_msg})
|
correct_score_msg = json.dumps({'correct': True, 'score': 1, 'msg': grader_msg})
|
||||||
incorrect_score_msg = json.dumps({'correct':False, 'score':0, 'msg': grader_msg})
|
incorrect_score_msg = json.dumps({'correct': False, 'score': 0, 'msg': grader_msg})
|
||||||
|
|
||||||
xserver_msgs = {'correct': correct_score_msg,
|
xserver_msgs = {'correct': correct_score_msg,
|
||||||
'incorrect': incorrect_score_msg,}
|
'incorrect': incorrect_score_msg, }
|
||||||
|
|
||||||
# Incorrect queuekey, state should not be updated
|
# Incorrect queuekey, state should not be updated
|
||||||
for correctness in ['correct', 'incorrect']:
|
for correctness in ['correct', 'incorrect']:
|
||||||
@@ -325,7 +326,7 @@ class CodeResponseTest(unittest.TestCase):
|
|||||||
|
|
||||||
new_cmap = CorrectMap()
|
new_cmap = CorrectMap()
|
||||||
new_cmap.update(old_cmap)
|
new_cmap.update(old_cmap)
|
||||||
npoints = 1 if correctness=='correct' else 0
|
npoints = 1 if correctness == 'correct' else 0
|
||||||
new_cmap.set(answer_id=answer_id, npoints=npoints, correctness=correctness, msg=grader_msg, queuestate=None)
|
new_cmap.set(answer_id=answer_id, npoints=npoints, correctness=correctness, msg=grader_msg, queuestate=None)
|
||||||
|
|
||||||
test_lcp.update_score(xserver_msgs[correctness], queuekey=1000 + i)
|
test_lcp.update_score(xserver_msgs[correctness], queuekey=1000 + i)
|
||||||
@@ -361,7 +362,7 @@ class CodeResponseTest(unittest.TestCase):
|
|||||||
for i, answer_id in enumerate(answer_ids):
|
for i, answer_id in enumerate(answer_ids):
|
||||||
queuekey = 1000 + i
|
queuekey = 1000 + i
|
||||||
latest_timestamp = datetime.now()
|
latest_timestamp = datetime.now()
|
||||||
queuestate = CodeResponseTest.make_queuestate(1000+i, latest_timestamp)
|
queuestate = CodeResponseTest.make_queuestate(1000 + i, latest_timestamp)
|
||||||
cmap.update(CorrectMap(answer_id=answer_id, queuestate=queuestate))
|
cmap.update(CorrectMap(answer_id=answer_id, queuestate=queuestate))
|
||||||
test_lcp.correct_map.update(cmap)
|
test_lcp.correct_map.update(cmap)
|
||||||
|
|
||||||
@@ -412,6 +413,7 @@ class ChoiceResponseTest(unittest.TestCase):
|
|||||||
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_3_1'), 'incorrect')
|
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_3_1'), 'incorrect')
|
||||||
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_4_1'), 'correct')
|
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_4_1'), 'correct')
|
||||||
|
|
||||||
|
|
||||||
class JavascriptResponseTest(unittest.TestCase):
|
class JavascriptResponseTest(unittest.TestCase):
|
||||||
|
|
||||||
def test_jr_grade(self):
|
def test_jr_grade(self):
|
||||||
@@ -424,4 +426,3 @@ class JavascriptResponseTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEquals(test_lcp.grade_answers(incorrect_answers).get_correctness('1_2_1'), 'incorrect')
|
self.assertEquals(test_lcp.grade_answers(incorrect_answers).get_correctness('1_2_1'), 'incorrect')
|
||||||
self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct')
|
self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct')
|
||||||
|
|
||||||
|
|||||||
@@ -57,9 +57,11 @@ def convert_files_to_filenames(answers):
|
|||||||
new_answers[answer_id] = answers[answer_id]
|
new_answers[answer_id] = answers[answer_id]
|
||||||
return new_answers
|
return new_answers
|
||||||
|
|
||||||
|
|
||||||
def is_list_of_files(files):
|
def is_list_of_files(files):
|
||||||
return isinstance(files, list) and all(is_file(f) for f in files)
|
return isinstance(files, list) and all(is_file(f) for f in files)
|
||||||
|
|
||||||
|
|
||||||
def is_file(file_to_test):
|
def is_file(file_to_test):
|
||||||
'''
|
'''
|
||||||
Duck typing to check if 'file_to_test' is a File object
|
Duck typing to check if 'file_to_test' is a File object
|
||||||
@@ -86,4 +88,3 @@ def find_with_default(node, path, default):
|
|||||||
return v.text
|
return v.text
|
||||||
else:
|
else:
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import requests
|
|||||||
log = logging.getLogger('mitx.' + __name__)
|
log = logging.getLogger('mitx.' + __name__)
|
||||||
dateformat = '%Y%m%d%H%M%S'
|
dateformat = '%Y%m%d%H%M%S'
|
||||||
|
|
||||||
|
|
||||||
def make_hashkey(seed):
|
def make_hashkey(seed):
|
||||||
'''
|
'''
|
||||||
Generate a string key by hashing
|
Generate a string key by hashing
|
||||||
@@ -29,9 +30,9 @@ def make_xheader(lms_callback_url, lms_key, queue_name):
|
|||||||
'queue_name': designate a specific queue within xqueue server, e.g. 'MITx-6.00x' (string)
|
'queue_name': designate a specific queue within xqueue server, e.g. 'MITx-6.00x' (string)
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
return json.dumps({ 'lms_callback_url': lms_callback_url,
|
return json.dumps({'lms_callback_url': lms_callback_url,
|
||||||
'lms_key': lms_key,
|
'lms_key': lms_key,
|
||||||
'queue_name': queue_name })
|
'queue_name': queue_name})
|
||||||
|
|
||||||
|
|
||||||
def parse_xreply(xreply):
|
def parse_xreply(xreply):
|
||||||
@@ -96,18 +97,18 @@ class XQueueInterface(object):
|
|||||||
|
|
||||||
|
|
||||||
def _login(self):
|
def _login(self):
|
||||||
payload = { 'username': self.auth['username'],
|
payload = {'username': self.auth['username'],
|
||||||
'password': self.auth['password'] }
|
'password': self.auth['password']}
|
||||||
return self._http_post(self.url + '/xqueue/login/', payload)
|
return self._http_post(self.url + '/xqueue/login/', payload)
|
||||||
|
|
||||||
|
|
||||||
def _send_to_queue(self, header, body, files_to_upload):
|
def _send_to_queue(self, header, body, files_to_upload):
|
||||||
payload = {'xqueue_header': header,
|
payload = {'xqueue_header': header,
|
||||||
'xqueue_body' : body}
|
'xqueue_body': body}
|
||||||
files = {}
|
files = {}
|
||||||
if files_to_upload is not None:
|
if files_to_upload is not None:
|
||||||
for f in files_to_upload:
|
for f in files_to_upload:
|
||||||
files.update({ f.name: f })
|
files.update({f.name: f})
|
||||||
|
|
||||||
return self._http_post(self.url + '/xqueue/submit/', payload, files=files)
|
return self._http_post(self.url + '/xqueue/submit/', payload, files=files)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ A handy util to print a django-debug-screen-like stack trace with
|
|||||||
values of local variables.
|
values of local variables.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys, traceback
|
import sys
|
||||||
|
import traceback
|
||||||
from django.utils.encoding import smart_unicode
|
from django.utils.encoding import smart_unicode
|
||||||
|
|
||||||
|
|
||||||
@@ -48,5 +49,3 @@ def supertrace(max_len=160):
|
|||||||
print s
|
print s
|
||||||
except:
|
except:
|
||||||
print "<ERROR WHILE PRINTING VALUE>"
|
print "<ERROR WHILE PRINTING VALUE>"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ TIMEDELTA_REGEX = re.compile(r'^((?P<days>\d+?) day(?:s?))?(\s)?((?P<hours>\d+?)
|
|||||||
# Generated this many different variants of problems with rerandomize=per_student
|
# Generated this many different variants of problems with rerandomize=per_student
|
||||||
NUM_RANDOMIZATION_BINS = 20
|
NUM_RANDOMIZATION_BINS = 20
|
||||||
|
|
||||||
|
|
||||||
def randomization_bin(seed, problem_id):
|
def randomization_bin(seed, problem_id):
|
||||||
"""
|
"""
|
||||||
Pick a randomization bin for the problem given the user's seed and a problem id.
|
Pick a randomization bin for the problem given the user's seed and a problem id.
|
||||||
@@ -43,6 +44,7 @@ def randomization_bin(seed, problem_id):
|
|||||||
# get the first few digits of the hash, convert to an int, then mod.
|
# get the first few digits of the hash, convert to an int, then mod.
|
||||||
return int(h.hexdigest()[:7], 16) % NUM_RANDOMIZATION_BINS
|
return int(h.hexdigest()[:7], 16) % NUM_RANDOMIZATION_BINS
|
||||||
|
|
||||||
|
|
||||||
def only_one(lst, default="", process=lambda x: x):
|
def only_one(lst, default="", process=lambda x: x):
|
||||||
"""
|
"""
|
||||||
If lst is empty, returns default
|
If lst is empty, returns default
|
||||||
@@ -315,7 +317,7 @@ class CapaModule(XModule):
|
|||||||
# check button is context-specific.
|
# check button is context-specific.
|
||||||
|
|
||||||
# Put a "Check" button if unlimited attempts or still some left
|
# Put a "Check" button if unlimited attempts or still some left
|
||||||
if self.max_attempts is None or self.attempts < self.max_attempts-1:
|
if self.max_attempts is None or self.attempts < self.max_attempts - 1:
|
||||||
check_button = "Check"
|
check_button = "Check"
|
||||||
else:
|
else:
|
||||||
# Will be final check so let user know that
|
# Will be final check so let user know that
|
||||||
@@ -561,7 +563,7 @@ class CapaModule(XModule):
|
|||||||
current_time = datetime.datetime.now()
|
current_time = datetime.datetime.now()
|
||||||
prev_submit_time = self.lcp.get_recentmost_queuetime()
|
prev_submit_time = self.lcp.get_recentmost_queuetime()
|
||||||
waittime_between_requests = self.system.xqueue['waittime']
|
waittime_between_requests = self.system.xqueue['waittime']
|
||||||
if (current_time-prev_submit_time).total_seconds() < waittime_between_requests:
|
if (current_time - prev_submit_time).total_seconds() < waittime_between_requests:
|
||||||
msg = 'You must wait at least %d seconds between submissions' % waittime_between_requests
|
msg = 'You must wait at least %d seconds between submissions' % waittime_between_requests
|
||||||
return {'success': msg, 'html': ''} # Prompts a modal dialog in ajax callback
|
return {'success': msg, 'html': ''} # Prompts a modal dialog in ajax callback
|
||||||
|
|
||||||
@@ -596,7 +598,7 @@ class CapaModule(XModule):
|
|||||||
event_info['attempts'] = self.attempts
|
event_info['attempts'] = self.attempts
|
||||||
self.system.track_function('save_problem_check', event_info)
|
self.system.track_function('save_problem_check', event_info)
|
||||||
|
|
||||||
if hasattr(self.system,'psychometrics_handler'): # update PsychometricsData using callback
|
if hasattr(self.system, 'psychometrics_handler'): # update PsychometricsData using callback
|
||||||
self.system.psychometrics_handler(self.get_instance_state())
|
self.system.psychometrics_handler(self.get_instance_state())
|
||||||
|
|
||||||
# render problem into HTML
|
# render problem into HTML
|
||||||
@@ -707,7 +709,7 @@ class CapaDescriptor(RawDescriptor):
|
|||||||
@property
|
@property
|
||||||
def editable_metadata_fields(self):
|
def editable_metadata_fields(self):
|
||||||
"""Remove metadata from the editable fields since it has its own editor"""
|
"""Remove metadata from the editable fields since it has its own editor"""
|
||||||
subset = super(CapaDescriptor,self).editable_metadata_fields
|
subset = super(CapaDescriptor, self).editable_metadata_fields
|
||||||
if 'markdown' in subset:
|
if 'markdown' in subset:
|
||||||
subset.remove('markdown')
|
subset.remove('markdown')
|
||||||
return subset
|
return subset
|
||||||
|
|||||||
@@ -50,14 +50,16 @@ ACCEPT_FILE_UPLOAD = False
|
|||||||
TRUE_DICT = ["True", True, "TRUE", "true"]
|
TRUE_DICT = ["True", True, "TRUE", "true"]
|
||||||
|
|
||||||
HUMAN_TASK_TYPE = {
|
HUMAN_TASK_TYPE = {
|
||||||
'selfassessment' : "Self Assessment",
|
'selfassessment': "Self Assessment",
|
||||||
'openended' : "External Grader",
|
'openended': "External Grader",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class IncorrectMaxScoreError(Exception):
|
class IncorrectMaxScoreError(Exception):
|
||||||
def __init__(self, msg):
|
def __init__(self, msg):
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
|
|
||||||
|
|
||||||
class CombinedOpenEndedModule(XModule):
|
class CombinedOpenEndedModule(XModule):
|
||||||
"""
|
"""
|
||||||
This is a module that encapsulates all open ended grading (self assessment, peer assessment, etc).
|
This is a module that encapsulates all open ended grading (self assessment, peer assessment, etc).
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import logging
|
import logging
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
log=logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class RubricParsingError(Exception):
|
class RubricParsingError(Exception):
|
||||||
def __init__(self, msg):
|
def __init__(self, msg):
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
|
|
||||||
|
|
||||||
class CombinedOpenEndedRubric(object):
|
class CombinedOpenEndedRubric(object):
|
||||||
|
|
||||||
def __init__ (self, system, view_only = False):
|
def __init__ (self, system, view_only = False):
|
||||||
@@ -28,7 +30,7 @@ class CombinedOpenEndedRubric(object):
|
|||||||
try:
|
try:
|
||||||
rubric_categories = self.extract_categories(rubric_xml)
|
rubric_categories = self.extract_categories(rubric_xml)
|
||||||
html = self.system.render_template('open_ended_rubric.html',
|
html = self.system.render_template('open_ended_rubric.html',
|
||||||
{'categories' : rubric_categories,
|
{'categories': rubric_categories,
|
||||||
'has_score': self.has_score,
|
'has_score': self.has_score,
|
||||||
'view_only': self.view_only})
|
'view_only': self.view_only})
|
||||||
success = True
|
success = True
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from pkg_resources import resource_string
|
|||||||
|
|
||||||
log = logging.getLogger('mitx.' + __name__)
|
log = logging.getLogger('mitx.' + __name__)
|
||||||
|
|
||||||
|
|
||||||
class ConditionalModule(XModule):
|
class ConditionalModule(XModule):
|
||||||
'''
|
'''
|
||||||
Blocks child module from showing unless certain conditions are met.
|
Blocks child module from showing unless certain conditions are met.
|
||||||
@@ -43,7 +44,7 @@ class ConditionalModule(XModule):
|
|||||||
"""
|
"""
|
||||||
XModule.__init__(self, system, location, definition, descriptor, instance_state, shared_state, **kwargs)
|
XModule.__init__(self, system, location, definition, descriptor, instance_state, shared_state, **kwargs)
|
||||||
self.contents = None
|
self.contents = None
|
||||||
self.condition = self.metadata.get('condition','')
|
self.condition = self.metadata.get('condition', '')
|
||||||
#log.debug('conditional module required=%s' % self.required_modules_list)
|
#log.debug('conditional module required=%s' % self.required_modules_list)
|
||||||
|
|
||||||
def _get_required_modules(self):
|
def _get_required_modules(self):
|
||||||
@@ -56,7 +57,7 @@ class ConditionalModule(XModule):
|
|||||||
def is_condition_satisfied(self):
|
def is_condition_satisfied(self):
|
||||||
self._get_required_modules()
|
self._get_required_modules()
|
||||||
|
|
||||||
if self.condition=='require_completed':
|
if self.condition == 'require_completed':
|
||||||
# all required modules must be completed, as determined by
|
# all required modules must be completed, as determined by
|
||||||
# the modules .is_completed() method
|
# the modules .is_completed() method
|
||||||
for module in self.required_modules:
|
for module in self.required_modules:
|
||||||
@@ -70,7 +71,7 @@ class ConditionalModule(XModule):
|
|||||||
else:
|
else:
|
||||||
log.debug('conditional module: %s IS completed' % module)
|
log.debug('conditional module: %s IS completed' % module)
|
||||||
return True
|
return True
|
||||||
elif self.condition=='require_attempted':
|
elif self.condition == 'require_attempted':
|
||||||
# all required modules must be attempted, as determined by
|
# all required modules must be attempted, as determined by
|
||||||
# the modules .is_attempted() method
|
# the modules .is_attempted() method
|
||||||
for module in self.required_modules:
|
for module in self.required_modules:
|
||||||
@@ -114,6 +115,7 @@ class ConditionalModule(XModule):
|
|||||||
|
|
||||||
return json.dumps({'html': html})
|
return json.dumps({'html': html})
|
||||||
|
|
||||||
|
|
||||||
class ConditionalDescriptor(SequenceDescriptor):
|
class ConditionalDescriptor(SequenceDescriptor):
|
||||||
module_class = ConditionalModule
|
module_class = ConditionalModule
|
||||||
|
|
||||||
@@ -125,7 +127,7 @@ class ConditionalDescriptor(SequenceDescriptor):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(ConditionalDescriptor, self).__init__(*args, **kwargs)
|
super(ConditionalDescriptor, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
required_module_list = [tuple(x.split('/',1)) for x in self.metadata.get('required','').split('&')]
|
required_module_list = [tuple(x.split('/', 1)) for x in self.metadata.get('required', '').split('&')]
|
||||||
self.required_module_locations = []
|
self.required_module_locations = []
|
||||||
for (tag, name) in required_module_list:
|
for (tag, name) in required_module_list:
|
||||||
loc = self.location.dict()
|
loc = self.location.dict()
|
||||||
@@ -138,4 +140,3 @@ class ConditionalDescriptor(SequenceDescriptor):
|
|||||||
"""Returns a list of XModuleDescritpor instances upon which this module depends, but are
|
"""Returns a list of XModuleDescritpor instances upon which this module depends, but are
|
||||||
not children of this module"""
|
not children of this module"""
|
||||||
return [self.system.load_item(loc) for loc in self.required_module_locations]
|
return [self.system.load_item(loc) for loc in self.required_module_locations]
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ from xmodule.modulestore import Location
|
|||||||
from .django import contentstore
|
from .django import contentstore
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
class StaticContent(object):
|
class StaticContent(object):
|
||||||
def __init__(self, loc, name, content_type, data, last_modified_at=None, thumbnail_location=None, import_path=None):
|
def __init__(self, loc, name, content_type, data, last_modified_at=None, thumbnail_location=None, import_path=None):
|
||||||
self.location = loc
|
self.location = loc
|
||||||
self.name = name #a display string which can be edited, and thus not part of the location which needs to be fixed
|
self.name = name # a display string which can be edited, and thus not part of the location which needs to be fixed
|
||||||
self.content_type = content_type
|
self.content_type = content_type
|
||||||
self.data = data
|
self.data = data
|
||||||
self.last_modified_at = last_modified_at
|
self.last_modified_at = last_modified_at
|
||||||
@@ -29,7 +30,7 @@ class StaticContent(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_thumbnail_name(original_name):
|
def generate_thumbnail_name(original_name):
|
||||||
return ('{0}'+XASSET_THUMBNAIL_TAIL_NAME).format(os.path.splitext(original_name)[0])
|
return ('{0}' + XASSET_THUMBNAIL_TAIL_NAME).format(os.path.splitext(original_name)[0])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def compute_location(org, course, name, revision=None, is_thumbnail=False):
|
def compute_location(org, course, name, revision=None, is_thumbnail=False):
|
||||||
@@ -56,9 +57,9 @@ class StaticContent(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_id_from_location(location):
|
def get_id_from_location(location):
|
||||||
return { 'tag':location.tag, 'org' : location.org, 'course' : location.course,
|
return {'tag': location.tag, 'org': location.org, 'course': location.course,
|
||||||
'category' : location.category, 'name' : location.name,
|
'category': location.category, 'name': location.name,
|
||||||
'revision' : location.revision}
|
'revision': location.revision}
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_location_from_path(path):
|
def get_location_from_path(path):
|
||||||
# remove leading / character if it is there one
|
# remove leading / character if it is there one
|
||||||
@@ -117,7 +118,7 @@ class ContentStore(object):
|
|||||||
thumbnail_name = StaticContent.generate_thumbnail_name(content.location.name)
|
thumbnail_name = StaticContent.generate_thumbnail_name(content.location.name)
|
||||||
|
|
||||||
thumbnail_file_location = StaticContent.compute_location(content.location.org, content.location.course,
|
thumbnail_file_location = StaticContent.compute_location(content.location.org, content.location.course,
|
||||||
thumbnail_name, is_thumbnail = True)
|
thumbnail_name, is_thumbnail=True)
|
||||||
|
|
||||||
# if we're uploading an image, then let's generate a thumbnail so that we can
|
# if we're uploading an image, then let's generate a thumbnail so that we can
|
||||||
# serve it up when needed without having to rescale on the fly
|
# serve it up when needed without having to rescale on the fly
|
||||||
@@ -149,7 +150,3 @@ class ContentStore(object):
|
|||||||
logging.exception("Failed to generate thumbnail for {0}. Exception: {1}".format(content.location, str(e)))
|
logging.exception("Failed to generate thumbnail for {0}. Exception: {1}".format(content.location, str(e)))
|
||||||
|
|
||||||
return thumbnail_content, thumbnail_file_location
|
return thumbnail_content, thumbnail_file_location
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from django.conf import settings
|
|||||||
|
|
||||||
_CONTENTSTORE = None
|
_CONTENTSTORE = None
|
||||||
|
|
||||||
|
|
||||||
def load_function(path):
|
def load_function(path):
|
||||||
"""
|
"""
|
||||||
Load a function by name.
|
Load a function by name.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import os
|
|||||||
|
|
||||||
class MongoContentStore(ContentStore):
|
class MongoContentStore(ContentStore):
|
||||||
def __init__(self, host, db, port=27017, user=None, password=None, **kwargs):
|
def __init__(self, host, db, port=27017, user=None, password=None, **kwargs):
|
||||||
logging.debug( 'Using MongoDB for static content serving at host={0} db={1}'.format(host,db))
|
logging.debug('Using MongoDB for static content serving at host={0} db={1}'.format(host, db))
|
||||||
_db = Connection(host=host, port=port, **kwargs)[db]
|
_db = Connection(host=host, port=port, **kwargs)[db]
|
||||||
|
|
||||||
if user is not None and password is not None:
|
if user is not None and password is not None:
|
||||||
@@ -33,7 +33,7 @@ class MongoContentStore(ContentStore):
|
|||||||
# Seems like with the GridFS we can't update existing ID's we have to do a delete/add pair
|
# Seems like with the GridFS we can't update existing ID's we have to do a delete/add pair
|
||||||
self.delete(id)
|
self.delete(id)
|
||||||
|
|
||||||
with self.fs.new_file(_id = id, filename=content.get_url_path(), content_type=content.content_type,
|
with self.fs.new_file(_id=id, filename=content.get_url_path(), content_type=content.content_type,
|
||||||
displayname=content.name, thumbnail_location=content.thumbnail_location, import_path=content.import_path) as fp:
|
displayname=content.name, thumbnail_location=content.thumbnail_location, import_path=content.import_path) as fp:
|
||||||
|
|
||||||
fp.write(content.data)
|
fp.write(content.data)
|
||||||
@@ -41,7 +41,7 @@ class MongoContentStore(ContentStore):
|
|||||||
return content
|
return content
|
||||||
|
|
||||||
def delete(self, id):
|
def delete(self, id):
|
||||||
if self.fs.exists({"_id" : id}):
|
if self.fs.exists({"_id": id}):
|
||||||
self.fs.delete(id)
|
self.fs.delete(id)
|
||||||
|
|
||||||
def find(self, location):
|
def find(self, location):
|
||||||
@@ -49,8 +49,8 @@ class MongoContentStore(ContentStore):
|
|||||||
try:
|
try:
|
||||||
with self.fs.get(id) as fp:
|
with self.fs.get(id) as fp:
|
||||||
return StaticContent(location, fp.displayname, fp.content_type, fp.read(),
|
return StaticContent(location, fp.displayname, fp.content_type, fp.read(),
|
||||||
fp.uploadDate, thumbnail_location = fp.thumbnail_location if hasattr(fp, 'thumbnail_location') else None,
|
fp.uploadDate, thumbnail_location=fp.thumbnail_location if hasattr(fp, 'thumbnail_location') else None,
|
||||||
import_path = fp.import_path if hasattr(fp, 'import_path') else None)
|
import_path=fp.import_path if hasattr(fp, 'import_path') else None)
|
||||||
except NoFile:
|
except NoFile:
|
||||||
raise NotFoundError()
|
raise NotFoundError()
|
||||||
|
|
||||||
@@ -76,12 +76,12 @@ class MongoContentStore(ContentStore):
|
|||||||
self.export(asset_location, output_directory)
|
self.export(asset_location, output_directory)
|
||||||
|
|
||||||
def get_all_content_thumbnails_for_course(self, location):
|
def get_all_content_thumbnails_for_course(self, location):
|
||||||
return self._get_all_content_for_course(location, get_thumbnails = True)
|
return self._get_all_content_for_course(location, get_thumbnails=True)
|
||||||
|
|
||||||
def get_all_content_for_course(self, location):
|
def get_all_content_for_course(self, location):
|
||||||
return self._get_all_content_for_course(location, get_thumbnails = False)
|
return self._get_all_content_for_course(location, get_thumbnails=False)
|
||||||
|
|
||||||
def _get_all_content_for_course(self, location, get_thumbnails = False):
|
def _get_all_content_for_course(self, location, get_thumbnails=False):
|
||||||
'''
|
'''
|
||||||
Returns a list of all static assets for a course. The return format is a list of dictionary elements. Example:
|
Returns a list of all static assets for a course. The return format is a list of dictionary elements. Example:
|
||||||
|
|
||||||
@@ -102,10 +102,7 @@ class MongoContentStore(ContentStore):
|
|||||||
]
|
]
|
||||||
'''
|
'''
|
||||||
course_filter = Location(XASSET_LOCATION_TAG, category="asset" if not get_thumbnails else "thumbnail",
|
course_filter = Location(XASSET_LOCATION_TAG, category="asset" if not get_thumbnails else "thumbnail",
|
||||||
course=location.course,org=location.org)
|
course=location.course, org=location.org)
|
||||||
# 'borrow' the function 'location_to_query' from the Mongo modulestore implementation
|
# 'borrow' the function 'location_to_query' from the Mongo modulestore implementation
|
||||||
items = self.fs_files.find(location_to_query(course_filter))
|
items = self.fs_files.find(location_to_query(course_filter))
|
||||||
return list(items)
|
return list(items)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -147,37 +147,37 @@ class CourseDescriptor(SequenceDescriptor):
|
|||||||
"""
|
"""
|
||||||
Return a dict which is a copy of the default grading policy
|
Return a dict which is a copy of the default grading policy
|
||||||
"""
|
"""
|
||||||
default = {"GRADER" : [
|
default = {"GRADER": [
|
||||||
{
|
{
|
||||||
"type" : "Homework",
|
"type": "Homework",
|
||||||
"min_count" : 12,
|
"min_count": 12,
|
||||||
"drop_count" : 2,
|
"drop_count": 2,
|
||||||
"short_label" : "HW",
|
"short_label": "HW",
|
||||||
"weight" : 0.15
|
"weight": 0.15
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type" : "Lab",
|
"type": "Lab",
|
||||||
"min_count" : 12,
|
"min_count": 12,
|
||||||
"drop_count" : 2,
|
"drop_count": 2,
|
||||||
"weight" : 0.15
|
"weight": 0.15
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type" : "Midterm Exam",
|
"type": "Midterm Exam",
|
||||||
"short_label" : "Midterm",
|
"short_label": "Midterm",
|
||||||
"min_count" : 1,
|
"min_count": 1,
|
||||||
"drop_count" : 0,
|
"drop_count": 0,
|
||||||
"weight" : 0.3
|
"weight": 0.3
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type" : "Final Exam",
|
"type": "Final Exam",
|
||||||
"short_label" : "Final",
|
"short_label": "Final",
|
||||||
"min_count" : 1,
|
"min_count": 1,
|
||||||
"drop_count" : 0,
|
"drop_count": 0,
|
||||||
"weight" : 0.4
|
"weight": 0.4
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"GRADE_CUTOFFS" : {
|
"GRADE_CUTOFFS": {
|
||||||
"Pass" : 0.5
|
"Pass": 0.5
|
||||||
}}
|
}}
|
||||||
return copy.deepcopy(default)
|
return copy.deepcopy(default)
|
||||||
|
|
||||||
@@ -230,8 +230,8 @@ class CourseDescriptor(SequenceDescriptor):
|
|||||||
|
|
||||||
# bleh, have to parse the XML here to just pull out the url_name attribute
|
# bleh, have to parse the XML here to just pull out the url_name attribute
|
||||||
# I don't think it's stored anywhere in the instance.
|
# I don't think it's stored anywhere in the instance.
|
||||||
course_file = StringIO(xml_data.encode('ascii','ignore'))
|
course_file = StringIO(xml_data.encode('ascii', 'ignore'))
|
||||||
xml_obj = etree.parse(course_file,parser=edx_xml_parser).getroot()
|
xml_obj = etree.parse(course_file, parser=edx_xml_parser).getroot()
|
||||||
|
|
||||||
policy_dir = None
|
policy_dir = None
|
||||||
url_name = xml_obj.get('url_name', xml_obj.get('slug'))
|
url_name = xml_obj.get('url_name', xml_obj.get('slug'))
|
||||||
@@ -329,7 +329,7 @@ class CourseDescriptor(SequenceDescriptor):
|
|||||||
def raw_grader(self, value):
|
def raw_grader(self, value):
|
||||||
# NOTE WELL: this change will not update the processed graders. If we need that, this needs to call grader_from_conf
|
# NOTE WELL: this change will not update the processed graders. If we need that, this needs to call grader_from_conf
|
||||||
self._grading_policy['RAW_GRADER'] = value
|
self._grading_policy['RAW_GRADER'] = value
|
||||||
self.definition['data'].setdefault('grading_policy',{})['GRADER'] = value
|
self.definition['data'].setdefault('grading_policy', {})['GRADER'] = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def grade_cutoffs(self):
|
def grade_cutoffs(self):
|
||||||
@@ -338,7 +338,7 @@ class CourseDescriptor(SequenceDescriptor):
|
|||||||
@grade_cutoffs.setter
|
@grade_cutoffs.setter
|
||||||
def grade_cutoffs(self, value):
|
def grade_cutoffs(self, value):
|
||||||
self._grading_policy['GRADE_CUTOFFS'] = value
|
self._grading_policy['GRADE_CUTOFFS'] = value
|
||||||
self.definition['data'].setdefault('grading_policy',{})['GRADE_CUTOFFS'] = value
|
self.definition['data'].setdefault('grading_policy', {})['GRADE_CUTOFFS'] = value
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -436,10 +436,10 @@ class CourseDescriptor(SequenceDescriptor):
|
|||||||
scale = 300.0 # about a year
|
scale = 300.0 # about a year
|
||||||
if announcement:
|
if announcement:
|
||||||
days = (now - announcement).days
|
days = (now - announcement).days
|
||||||
score = -exp(-days/scale)
|
score = -exp(-days / scale)
|
||||||
else:
|
else:
|
||||||
days = (now - start).days
|
days = (now - start).days
|
||||||
score = exp(days/scale)
|
score = exp(days / scale)
|
||||||
return score
|
return score
|
||||||
|
|
||||||
def _sorting_dates(self):
|
def _sorting_dates(self):
|
||||||
@@ -501,16 +501,16 @@ class CourseDescriptor(SequenceDescriptor):
|
|||||||
xmoduledescriptors.append(s)
|
xmoduledescriptors.append(s)
|
||||||
|
|
||||||
# The xmoduledescriptors included here are only the ones that have scores.
|
# The xmoduledescriptors included here are only the ones that have scores.
|
||||||
section_description = { 'section_descriptor' : s, 'xmoduledescriptors' : filter(lambda child: child.has_score, xmoduledescriptors) }
|
section_description = {'section_descriptor': s, 'xmoduledescriptors': filter(lambda child: child.has_score, xmoduledescriptors)}
|
||||||
|
|
||||||
section_format = s.metadata.get('format', "")
|
section_format = s.metadata.get('format', "")
|
||||||
graded_sections[ section_format ] = graded_sections.get( section_format, [] ) + [section_description]
|
graded_sections[section_format] = graded_sections.get(section_format, []) + [section_description]
|
||||||
|
|
||||||
all_descriptors.extend(xmoduledescriptors)
|
all_descriptors.extend(xmoduledescriptors)
|
||||||
all_descriptors.append(s)
|
all_descriptors.append(s)
|
||||||
|
|
||||||
return { 'graded_sections' : graded_sections,
|
return {'graded_sections': graded_sections,
|
||||||
'all_descriptors' : all_descriptors,}
|
'all_descriptors': all_descriptors, }
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -715,4 +715,3 @@ class CourseDescriptor(SequenceDescriptor):
|
|||||||
@property
|
@property
|
||||||
def org(self):
|
def org(self):
|
||||||
return self.location.org
|
return self.location.org
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from xmodule.raw_module import RawDescriptor
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
class DiscussionModule(XModule):
|
class DiscussionModule(XModule):
|
||||||
js = {'coffee':
|
js = {'coffee':
|
||||||
[resource_string(__name__, 'js/src/time.coffee'),
|
[resource_string(__name__, 'js/src/time.coffee'),
|
||||||
@@ -30,6 +31,7 @@ class DiscussionModule(XModule):
|
|||||||
self.title = xml_data.attrib['for']
|
self.title = xml_data.attrib['for']
|
||||||
self.discussion_category = xml_data.attrib['discussion_category']
|
self.discussion_category = xml_data.attrib['discussion_category']
|
||||||
|
|
||||||
|
|
||||||
class DiscussionDescriptor(RawDescriptor):
|
class DiscussionDescriptor(RawDescriptor):
|
||||||
module_class = DiscussionModule
|
module_class = DiscussionModule
|
||||||
template_dir_name = "discussion"
|
template_dir_name = "discussion"
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
ErrorLog = namedtuple('ErrorLog', 'tracker errors')
|
ErrorLog = namedtuple('ErrorLog', 'tracker errors')
|
||||||
|
|
||||||
|
|
||||||
def exc_info_to_str(exc_info):
|
def exc_info_to_str(exc_info):
|
||||||
"""Given some exception info, convert it into a string using
|
"""Given some exception info, convert it into a string using
|
||||||
the traceback.format_exception() function.
|
the traceback.format_exception() function.
|
||||||
"""
|
"""
|
||||||
return ''.join(traceback.format_exception(*exc_info))
|
return ''.join(traceback.format_exception(*exc_info))
|
||||||
|
|
||||||
|
|
||||||
def in_exception_handler():
|
def in_exception_handler():
|
||||||
'''Is there an active exception?'''
|
'''Is there an active exception?'''
|
||||||
return sys.exc_info() != (None, None, None)
|
return sys.exc_info() != (None, None, None)
|
||||||
@@ -44,6 +46,7 @@ def make_error_tracker():
|
|||||||
|
|
||||||
return ErrorLog(error_tracker, errors)
|
return ErrorLog(error_tracker, errors)
|
||||||
|
|
||||||
|
|
||||||
def null_error_tracker(msg):
|
def null_error_tracker(msg):
|
||||||
'''A dummy error tracker that just ignores the messages'''
|
'''A dummy error tracker that just ignores the messages'''
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ def invalid_args(func, argdict):
|
|||||||
if keywords: return set() # All accepted
|
if keywords: return set() # All accepted
|
||||||
return set(argdict) - set(args)
|
return set(argdict) - set(args)
|
||||||
|
|
||||||
|
|
||||||
def grader_from_conf(conf):
|
def grader_from_conf(conf):
|
||||||
"""
|
"""
|
||||||
This creates a CourseGrader from a configuration (such as in course_settings.py).
|
This creates a CourseGrader from a configuration (such as in course_settings.py).
|
||||||
@@ -211,7 +212,7 @@ class SingleSectionGrader(CourseGrader):
|
|||||||
|
|
||||||
if foundScore or generate_random_scores:
|
if foundScore or generate_random_scores:
|
||||||
if generate_random_scores: # for debugging!
|
if generate_random_scores: # for debugging!
|
||||||
earned = random.randint(2,15)
|
earned = random.randint(2, 15)
|
||||||
possible = random.randint(earned, 15)
|
possible = random.randint(earned, 15)
|
||||||
else: # We found the score
|
else: # We found the score
|
||||||
earned = foundScore.earned
|
earned = foundScore.earned
|
||||||
@@ -297,7 +298,7 @@ class AssignmentFormatGrader(CourseGrader):
|
|||||||
for i in range(max(self.min_count, len(scores))):
|
for i in range(max(self.min_count, len(scores))):
|
||||||
if i < len(scores) or generate_random_scores:
|
if i < len(scores) or generate_random_scores:
|
||||||
if generate_random_scores: # for debugging!
|
if generate_random_scores: # for debugging!
|
||||||
earned = random.randint(2,15)
|
earned = random.randint(2, 15)
|
||||||
possible = random.randint(earned, 15)
|
possible = random.randint(earned, 15)
|
||||||
section_name = "Generated"
|
section_name = "Generated"
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ from lxml import etree
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class GradingServiceError(Exception):
|
class GradingServiceError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class GradingService(object):
|
class GradingService(object):
|
||||||
"""
|
"""
|
||||||
Interface to staff grading backend.
|
Interface to staff grading backend.
|
||||||
@@ -35,7 +37,7 @@ class GradingService(object):
|
|||||||
"""
|
"""
|
||||||
response = self.session.post(self.login_url,
|
response = self.session.post(self.login_url,
|
||||||
{'username': self.username,
|
{'username': self.username,
|
||||||
'password': self.password,})
|
'password': self.password, })
|
||||||
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
|
||||||
def check_html(html):
|
def check_html(html):
|
||||||
'''
|
'''
|
||||||
Check whether the passed in html string can be parsed by lxml.
|
Check whether the passed in html string can be parsed by lxml.
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ class HtmlDescriptor(XmlDescriptor, EditingDescriptor):
|
|||||||
|
|
||||||
# TODO (ichuang): remove this after migration
|
# TODO (ichuang): remove this after migration
|
||||||
# for Fall 2012 LMS migration: keep filename (and unmangled filename)
|
# for Fall 2012 LMS migration: keep filename (and unmangled filename)
|
||||||
definition['filename'] = [ filepath, filename ]
|
definition['filename'] = [filepath, filename]
|
||||||
|
|
||||||
return definition
|
return definition
|
||||||
|
|
||||||
@@ -180,6 +180,7 @@ class AboutDescriptor(HtmlDescriptor):
|
|||||||
"""
|
"""
|
||||||
template_dir_name = "about"
|
template_dir_name = "about"
|
||||||
|
|
||||||
|
|
||||||
class StaticTabDescriptor(HtmlDescriptor):
|
class StaticTabDescriptor(HtmlDescriptor):
|
||||||
"""
|
"""
|
||||||
These pieces of course content are treated as HtmlModules but we need to overload where the templates are located
|
These pieces of course content are treated as HtmlModules but we need to overload where the templates are located
|
||||||
@@ -187,6 +188,7 @@ class StaticTabDescriptor(HtmlDescriptor):
|
|||||||
"""
|
"""
|
||||||
template_dir_name = "statictab"
|
template_dir_name = "statictab"
|
||||||
|
|
||||||
|
|
||||||
class CourseInfoDescriptor(HtmlDescriptor):
|
class CourseInfoDescriptor(HtmlDescriptor):
|
||||||
"""
|
"""
|
||||||
These pieces of course content are treated as HtmlModules but we need to overload where the templates are located
|
These pieces of course content are treated as HtmlModules but we need to overload where the templates are located
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class MakoModuleDescriptor(XModuleDescriptor):
|
|||||||
"""
|
"""
|
||||||
return {'module': self,
|
return {'module': self,
|
||||||
'metadata': self.metadata,
|
'metadata': self.metadata,
|
||||||
'editable_metadata_fields' : self.editable_metadata_fields
|
'editable_metadata_fields': self.editable_metadata_fields
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_html(self):
|
def get_html(self):
|
||||||
@@ -46,4 +46,3 @@ class MakoModuleDescriptor(XModuleDescriptor):
|
|||||||
def editable_metadata_fields(self):
|
def editable_metadata_fields(self):
|
||||||
subset = [name for name in self.metadata.keys() if name not in self.system_metadata_fields]
|
subset = [name for name in self.metadata.keys() if name not in self.system_metadata_fields]
|
||||||
return subset
|
return subset
|
||||||
|
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ class MongoModuleStore(ModuleStoreBase):
|
|||||||
if location.category == 'static_tab':
|
if location.category == 'static_tab':
|
||||||
course = self.get_course_for_item(item.location)
|
course = self.get_course_for_item(item.location)
|
||||||
existing_tabs = course.tabs or []
|
existing_tabs = course.tabs or []
|
||||||
existing_tabs.append({'type':'static_tab', 'name' : item.metadata.get('display_name'), 'url_slug' : item.location.name})
|
existing_tabs.append({'type': 'static_tab', 'name': item.metadata.get('display_name'), 'url_slug': item.location.name})
|
||||||
course.tabs = existing_tabs
|
course.tabs = existing_tabs
|
||||||
self.update_metadata(course.location, course.metadata)
|
self.update_metadata(course.location, course.metadata)
|
||||||
|
|
||||||
|
|||||||
@@ -104,14 +104,14 @@ def path_to_location(modulestore, course_id, location):
|
|||||||
# module nested in more than one positional module will work.
|
# module nested in more than one positional module will work.
|
||||||
if n > 3:
|
if n > 3:
|
||||||
position_list = []
|
position_list = []
|
||||||
for path_index in range(2, n-1):
|
for path_index in range(2, n - 1):
|
||||||
category = path[path_index].category
|
category = path[path_index].category
|
||||||
if category == 'sequential' or category == 'videosequence':
|
if category == 'sequential' or category == 'videosequence':
|
||||||
section_desc = modulestore.get_instance(course_id, path[path_index])
|
section_desc = modulestore.get_instance(course_id, path[path_index])
|
||||||
child_locs = [c.location for c in section_desc.get_children()]
|
child_locs = [c.location for c in section_desc.get_children()]
|
||||||
# positions are 1-indexed, and should be strings to be consistent with
|
# positions are 1-indexed, and should be strings to be consistent with
|
||||||
# url parsing.
|
# url parsing.
|
||||||
position_list.append(str(child_locs.index(path[path_index+1]) + 1))
|
position_list.append(str(child_locs.index(path[path_index + 1]) + 1))
|
||||||
position = "_".join(position_list)
|
position = "_".join(position_list)
|
||||||
|
|
||||||
return (course_id, chapter, section, position)
|
return (course_id, chapter, section, position)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from xmodule.contentstore.content import StaticContent
|
|||||||
from xmodule.modulestore import Location
|
from xmodule.modulestore import Location
|
||||||
from xmodule.modulestore.mongo import MongoModuleStore
|
from xmodule.modulestore.mongo import MongoModuleStore
|
||||||
|
|
||||||
|
|
||||||
def clone_course(modulestore, contentstore, source_location, dest_location, delete_original=False):
|
def clone_course(modulestore, contentstore, source_location, dest_location, delete_original=False):
|
||||||
# first check to see if the modulestore is Mongo backed
|
# first check to see if the modulestore is Mongo backed
|
||||||
if not isinstance(modulestore, MongoModuleStore):
|
if not isinstance(modulestore, MongoModuleStore):
|
||||||
@@ -31,12 +32,12 @@ def clone_course(modulestore, contentstore, source_location, dest_location, dele
|
|||||||
original_loc = Location(module.location)
|
original_loc = Location(module.location)
|
||||||
|
|
||||||
if original_loc.category != 'course':
|
if original_loc.category != 'course':
|
||||||
module.location = module.location._replace(tag = dest_location.tag, org = dest_location.org,
|
module.location = module.location._replace(tag=dest_location.tag, org=dest_location.org,
|
||||||
course = dest_location.course)
|
course=dest_location.course)
|
||||||
else:
|
else:
|
||||||
# on the course module we also have to update the module name
|
# on the course module we also have to update the module name
|
||||||
module.location = module.location._replace(tag = dest_location.tag, org = dest_location.org,
|
module.location = module.location._replace(tag=dest_location.tag, org=dest_location.org,
|
||||||
course = dest_location.course, name=dest_location.name)
|
course=dest_location.course, name=dest_location.name)
|
||||||
|
|
||||||
print "Cloning module {0} to {1}....".format(original_loc, module.location)
|
print "Cloning module {0} to {1}....".format(original_loc, module.location)
|
||||||
|
|
||||||
@@ -48,8 +49,8 @@ def clone_course(modulestore, contentstore, source_location, dest_location, dele
|
|||||||
new_children = []
|
new_children = []
|
||||||
for child_loc_url in module.definition['children']:
|
for child_loc_url in module.definition['children']:
|
||||||
child_loc = Location(child_loc_url)
|
child_loc = Location(child_loc_url)
|
||||||
child_loc = child_loc._replace(tag = dest_location.tag, org = dest_location.org,
|
child_loc = child_loc._replace(tag=dest_location.tag, org=dest_location.org,
|
||||||
course = dest_location.course)
|
course=dest_location.course)
|
||||||
new_children = new_children + [child_loc.url()]
|
new_children = new_children + [child_loc.url()]
|
||||||
|
|
||||||
modulestore.update_children(module.location, new_children)
|
modulestore.update_children(module.location, new_children)
|
||||||
@@ -63,8 +64,8 @@ def clone_course(modulestore, contentstore, source_location, dest_location, dele
|
|||||||
for thumb in thumbs:
|
for thumb in thumbs:
|
||||||
thumb_loc = Location(thumb["_id"])
|
thumb_loc = Location(thumb["_id"])
|
||||||
content = contentstore.find(thumb_loc)
|
content = contentstore.find(thumb_loc)
|
||||||
content.location = content.location._replace(org = dest_location.org,
|
content.location = content.location._replace(org=dest_location.org,
|
||||||
course = dest_location.course)
|
course=dest_location.course)
|
||||||
|
|
||||||
print "Cloning thumbnail {0} to {1}".format(thumb_loc, content.location)
|
print "Cloning thumbnail {0} to {1}".format(thumb_loc, content.location)
|
||||||
|
|
||||||
@@ -76,13 +77,13 @@ def clone_course(modulestore, contentstore, source_location, dest_location, dele
|
|||||||
for asset in assets:
|
for asset in assets:
|
||||||
asset_loc = Location(asset["_id"])
|
asset_loc = Location(asset["_id"])
|
||||||
content = contentstore.find(asset_loc)
|
content = contentstore.find(asset_loc)
|
||||||
content.location = content.location._replace(org = dest_location.org,
|
content.location = content.location._replace(org=dest_location.org,
|
||||||
course = dest_location.course)
|
course=dest_location.course)
|
||||||
|
|
||||||
# be sure to update the pointer to the thumbnail
|
# be sure to update the pointer to the thumbnail
|
||||||
if content.thumbnail_location is not None:
|
if content.thumbnail_location is not None:
|
||||||
content.thumbnail_location = content.thumbnail_location._replace(org = dest_location.org,
|
content.thumbnail_location = content.thumbnail_location._replace(org=dest_location.org,
|
||||||
course = dest_location.course)
|
course=dest_location.course)
|
||||||
|
|
||||||
print "Cloning asset {0} to {1}".format(asset_loc, content.location)
|
print "Cloning asset {0} to {1}".format(asset_loc, content.location)
|
||||||
|
|
||||||
@@ -90,6 +91,7 @@ def clone_course(modulestore, contentstore, source_location, dest_location, dele
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def delete_course(modulestore, contentstore, source_location):
|
def delete_course(modulestore, contentstore, source_location):
|
||||||
# first check to see if the modulestore is Mongo backed
|
# first check to see if the modulestore is Mongo backed
|
||||||
if not isinstance(modulestore, MongoModuleStore):
|
if not isinstance(modulestore, MongoModuleStore):
|
||||||
|
|||||||
@@ -8,5 +8,3 @@ for i in range(5):
|
|||||||
TEST_DIR = TEST_DIR / 'test'
|
TEST_DIR = TEST_DIR / 'test'
|
||||||
|
|
||||||
DATA_DIR = TEST_DIR / 'data'
|
DATA_DIR = TEST_DIR / 'data'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,15 @@ from xmodule.modulestore import Location
|
|||||||
from xmodule.modulestore.django import modulestore
|
from xmodule.modulestore.django import modulestore
|
||||||
from xmodule.timeparse import stringify_time
|
from xmodule.timeparse import stringify_time
|
||||||
|
|
||||||
|
|
||||||
def XMODULE_COURSE_CREATION(class_to_create, **kwargs):
|
def XMODULE_COURSE_CREATION(class_to_create, **kwargs):
|
||||||
return XModuleCourseFactory._create(class_to_create, **kwargs)
|
return XModuleCourseFactory._create(class_to_create, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def XMODULE_ITEM_CREATION(class_to_create, **kwargs):
|
def XMODULE_ITEM_CREATION(class_to_create, **kwargs):
|
||||||
return XModuleItemFactory._create(class_to_create, **kwargs)
|
return XModuleItemFactory._create(class_to_create, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class XModuleCourseFactory(Factory):
|
class XModuleCourseFactory(Factory):
|
||||||
"""
|
"""
|
||||||
Factory for XModule courses.
|
Factory for XModule courses.
|
||||||
@@ -53,9 +56,11 @@ class XModuleCourseFactory(Factory):
|
|||||||
|
|
||||||
return new_course
|
return new_course
|
||||||
|
|
||||||
|
|
||||||
class Course:
|
class Course:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CourseFactory(XModuleCourseFactory):
|
class CourseFactory(XModuleCourseFactory):
|
||||||
FACTORY_FOR = Course
|
FACTORY_FOR = Course
|
||||||
|
|
||||||
@@ -64,6 +69,7 @@ class CourseFactory(XModuleCourseFactory):
|
|||||||
number = '999'
|
number = '999'
|
||||||
display_name = 'Robot Super Course'
|
display_name = 'Robot Super Course'
|
||||||
|
|
||||||
|
|
||||||
class XModuleItemFactory(Factory):
|
class XModuleItemFactory(Factory):
|
||||||
"""
|
"""
|
||||||
Factory for XModule items.
|
Factory for XModule items.
|
||||||
@@ -107,9 +113,11 @@ class XModuleItemFactory(Factory):
|
|||||||
|
|
||||||
return new_item
|
return new_item
|
||||||
|
|
||||||
|
|
||||||
class Item:
|
class Item:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ItemFactory(XModuleItemFactory):
|
class ItemFactory(XModuleItemFactory):
|
||||||
FACTORY_FOR = Item
|
FACTORY_FOR = Item
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user