Commit f91060d1 by cahrens

Merge branch 'master' into feature/cas/manual-policy

Conflicts:
	cms/djangoapps/contentstore/features/common.py
	cms/djangoapps/contentstore/features/section.py
	cms/djangoapps/contentstore/tests/test_course_settings.py
	cms/djangoapps/contentstore/views.py
	cms/static/js/models/settings/course_grading_policy.js
	cms/static/js/views/settings/main_settings_view.js
	cms/static/sass/_settings.scss
	cms/templates/settings.html
	cms/urls.py
parents 38632c52 94a28f60
[pep8]
ignore=E501
\ No newline at end of file
...@@ -9,6 +9,7 @@ gfortran ...@@ -9,6 +9,7 @@ gfortran
liblapack-dev liblapack-dev
libfreetype6-dev libfreetype6-dev
libpng12-dev libpng12-dev
libjpeg-dev
libxml2-dev libxml2-dev
libxslt-dev libxslt-dev
yui-compressor yui-compressor
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
[run] [run]
data_file = reports/cms/.coverage data_file = reports/cms/.coverage
source = cms,common/djangoapps source = cms,common/djangoapps
omit = cms/envs/*, cms/manage.py omit = cms/envs/*, cms/manage.py, common/djangoapps/*/migrations/*
[report] [report]
ignore_errors = True ignore_errors = True
......
...@@ -18,6 +18,8 @@ STAFF_ROLE_NAME = 'staff' ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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 ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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.
......
...@@ -16,7 +16,8 @@ def i_visit_the_studio_homepage(step): ...@@ -16,7 +16,8 @@ def i_visit_the_studio_homepage(step):
# LETTUCE_SERVER_PORT = 8001 # LETTUCE_SERVER_PORT = 8001
# in your settings.py file. # in your settings.py file.
world.browser.visit(django_url('/')) world.browser.visit(django_url('/'))
assert world.browser.is_element_present_by_css('body.no-header', 10) signin_css = 'a.action-signin'
assert world.browser.is_element_present_by_css(signin_css, 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):
...@@ -31,7 +32,7 @@ def i_press_the_category_delete_icon(step, category): ...@@ -31,7 +32,7 @@ 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)
...@@ -116,7 +117,11 @@ def log_into_studio( ...@@ -116,7 +117,11 @@ def log_into_studio(
create_studio_user(uname=uname, email=email, is_staff=is_staff) create_studio_user(uname=uname, email=email, is_staff=is_staff)
world.browser.cookies.delete() world.browser.cookies.delete()
world.browser.visit(django_url('/')) world.browser.visit(django_url('/'))
world.browser.is_element_present_by_css('body.no-header', 10) signin_css = 'a.action-signin'
world.browser.is_element_present_by_css(signin_css, 10)
# click the signin button
css_click(signin_css)
login_form = world.browser.find_by_css('form#login_form') login_form = world.browser.find_by_css('form#login_form')
login_form.find_by_name('email').fill(email) login_form.find_by_name('email').fill(email)
...@@ -129,7 +134,8 @@ def create_a_course(): ...@@ -129,7 +134,8 @@ 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)) course_title_css = 'span.course-title'
assert_true(world.browser.is_element_present_by_css(course_title_css, 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'
...@@ -138,6 +144,8 @@ def add_section(name='My Section'): ...@@ -138,6 +144,8 @@ def add_section(name='My Section'):
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)
span_css = 'span.section-name-span'
assert_true(world.browser.is_element_present_by_css(span_css, 5))
def add_subsection(name='Subsection One'): def add_subsection(name='Subsection One'):
css = 'a.new-subsection-item' css = 'a.new-subsection-item'
......
...@@ -2,49 +2,61 @@ from lettuce import world, step ...@@ -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' course_title_css = 'span.course-title'
assert world.browser.is_element_present_by_css(courseware_css) assert world.browser.is_element_present_by_css(course_title_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 ...@@ -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): ...@@ -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
......
...@@ -5,8 +5,8 @@ Feature: Sign in ...@@ -5,8 +5,8 @@ Feature: Sign in
Scenario: Sign up from the homepage Scenario: Sign up from the homepage
Given I visit the Studio homepage Given I visit the Studio homepage
When I click the link with the text "Sign up" When I click the link with the text "Sign Up"
And I fill in the registration form And I fill in the registration form
And I press the "Create My Account" button on the registration form And I press the Create My Account button on the registration form
Then I should see be on the studio home page Then I should see be on the studio home page
And I should see the message "please click on the activation link in your email." And I should see the message "please click on the activation link in your email."
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,19 @@ def i_fill_in_the_registration_form(step): ...@@ -9,15 +10,19 @@ 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$')
def i_press_the_button_on_the_registration_form(step, button): @step('I press the Create My Account button on the registration form$')
def i_press_the_button_on_the_registration_form(step):
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() submit_css = 'button#submit'
register_form.find_by_css(submit_css).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 ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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 ...@@ -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): ...@@ -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 ...@@ -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 ...@@ -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): ...@@ -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)
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.
......
import logging
from static_replace import replace_static_urls from static_replace import replace_static_urls
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore from django.http import Http404
from lxml import etree
import re
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)
else: else:
module = store.get_item(location) module = store.get_item(location)
except ItemNotFoundError: except ItemNotFoundError:
raise Http404 # create a new one
template_location = Location(['i4x', 'edx', 'templates', location.category, 'Empty'])
module = store.clone_item(template_location, location)
data = module.definition['data'] data = module.definition['data']
if rewrite_static_links: if rewrite_static_links:
...@@ -36,9 +35,9 @@ def get_module_info(store, location, parent_location = None, rewrite_static_link ...@@ -36,9 +35,9 @@ 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
try: try:
if location.revision is None: if location.revision is None:
module = store.get_item(location) module = store.get_item(location)
...@@ -52,7 +51,6 @@ def set_module_info(store, location, post_data): ...@@ -52,7 +51,6 @@ def set_module_info(store, location, post_data):
# presume that we have an 'Empty' template # presume that we have an 'Empty' template
template_location = Location(['i4x', 'edx', 'templates', location.category, 'Empty']) template_location = Location(['i4x', 'edx', 'templates', location.category, 'Empty'])
module = store.clone_item(template_location, location) module = store.clone_item(template_location, location)
isNew = True
if post_data.get('data') is not None: if post_data.get('data') is not None:
data = post_data['data'] data = post_data['data']
......
from factory import Factory from factory import Factory
from xmodule.modulestore import Location from datetime import datetime
from xmodule.modulestore.django import modulestore
from time import gmtime
from uuid import uuid4 from uuid import uuid4
from xmodule.timeparse import stringify_time from student.models import (User, UserProfile, Registration,
CourseEnrollmentAllowed)
from django.contrib.auth.models import Group
def XMODULE_COURSE_CREATION(class_to_create, **kwargs): class UserProfileFactory(Factory):
return XModuleCourseFactory._create(class_to_create, **kwargs) FACTORY_FOR = UserProfile
def XMODULE_ITEM_CREATION(class_to_create, **kwargs): user = None
return XModuleItemFactory._create(class_to_create, **kwargs) name = 'Robot Studio'
courseware = 'course.xml'
class XModuleCourseFactory(Factory):
"""
Factory for XModule courses.
"""
ABSTRACT_FACTORY = True class RegistrationFactory(Factory):
_creation_function = (XMODULE_COURSE_CREATION,) FACTORY_FOR = Registration
@classmethod user = None
def _create(cls, target_class, *args, **kwargs): activation_key = uuid4().hex
template = Location('i4x', 'edx', 'templates', 'course', 'Empty')
org = kwargs.get('org')
number = kwargs.get('number')
display_name = kwargs.get('display_name')
location = Location('i4x', org, number,
'course', Location.clean(display_name))
store = modulestore('direct') class UserFactory(Factory):
FACTORY_FOR = User
# Write the data to the mongo datastore username = 'robot'
new_course = store.clone_item(template, location) email = 'robot@edx.org'
password = 'test'
first_name = 'Robot'
last_name = 'Tester'
is_staff = False
is_active = True
is_superuser = False
last_login = datetime.now()
date_joined = datetime.now()
# This metadata code was copied from cms/djangoapps/contentstore/views.py
if display_name is not None:
new_course.metadata['display_name'] = display_name
new_course.metadata['data_dir'] = uuid4().hex class GroupFactory(Factory):
new_course.metadata['start'] = stringify_time(gmtime()) FACTORY_FOR = Group
new_course.tabs = [{"type": "courseware"},
{"type": "course_info", "name": "Course Info"},
{"type": "discussion", "name": "Discussion"},
{"type": "wiki", "name": "Wiki"},
{"type": "progress", "name": "Progress"}]
# Update the data in the mongo datastore name = 'test_group'
store.update_metadata(new_course.location.url(), new_course.own_metadata)
return new_course
class Course: class CourseEnrollmentAllowedFactory(Factory):
pass FACTORY_FOR = CourseEnrollmentAllowed
class CourseFactory(XModuleCourseFactory): email = 'test@edx.org'
FACTORY_FOR = Course course_id = 'edX/test/2012_Fall'
template = 'i4x://edx/templates/course/Empty'
org = 'MITx'
number = '999'
display_name = 'Robot Super Course'
class XModuleItemFactory(Factory):
"""
Factory for XModule items.
"""
ABSTRACT_FACTORY = True
_creation_function = (XMODULE_ITEM_CREATION,)
@classmethod
def _create(cls, target_class, *args, **kwargs):
"""
kwargs must include parent_location, template. Can contain display_name
target_class is ignored
"""
DETACHED_CATEGORIES = ['about', 'static_tab', 'course_info']
parent_location = Location(kwargs.get('parent_location'))
template = Location(kwargs.get('template'))
display_name = kwargs.get('display_name')
store = modulestore('direct')
# This code was based off that in cms/djangoapps/contentstore/views.py
parent = store.get_item(parent_location)
dest_location = parent_location._replace(category=template.category, name=uuid4().hex)
new_item = store.clone_item(template, dest_location)
# TODO: This needs to be deleted when we have proper storage for static content
new_item.metadata['data_dir'] = parent.metadata['data_dir']
# replace the display name with an optional parameter passed in from the caller
if display_name is not None:
new_item.metadata['display_name'] = display_name
store.update_metadata(new_item.location.url(), new_item.own_metadata)
if new_item.location.category not in DETACHED_CATEGORIES:
store.update_children(parent_location, parent.definition.get('children', []) + [new_item.location.url()])
return new_item
class Item:
pass
class ItemFactory(XModuleItemFactory):
FACTORY_FOR = Item
parent_location = 'i4x://MITx/999/course/Robot_Super_Course'
template = 'i4x://edx/templates/chapter/Empty'
display_name = 'Section One'
\ No newline at end of file
from django.test.testcases import TestCase
from cache_toolbox.core import get_cached_content, set_cached_content, del_cached_content from cache_toolbox.core import get_cached_content, set_cached_content, del_cached_content
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.contentstore.content import StaticContent from xmodule.contentstore.content import StaticContent
from django.test import TestCase
class Content: class Content:
def __init__(self, location, content): def __init__(self, location, content):
...@@ -11,6 +12,7 @@ class Content: ...@@ -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')
...@@ -32,7 +34,3 @@ class CachingTestCase(TestCase): ...@@ -32,7 +34,3 @@ class CachingTestCase(TestCase):
'should not be stored in cache with unicodeLocation') 'should not be stored in cache with unicodeLocation')
self.assertEqual(None, get_cached_content(self.nonUnicodeLocation), self.assertEqual(None, get_cached_content(self.nonUnicodeLocation),
'should not be stored in cache with nonUnicodeLocation') 'should not be stored in cache with nonUnicodeLocation')
from django.test.testcases import TestCase
import datetime import datetime
import time
from django.contrib.auth.models import User
import xmodule
from django.test.client import Client
from django.core.urlresolvers import reverse
from xmodule.modulestore import Location
from cms.djangoapps.models.settings.course_details import CourseDetails,\
CourseSettingsEncoder
import json import json
import copy
from util import converters from util import converters
import calendar
from util.converters import jsdate_to_time from util.converters import jsdate_to_time
from django.contrib.auth.models import User
from django.test.client import Client
from django.core.urlresolvers import reverse
from django.utils.timezone import UTC from django.utils.timezone import UTC
from xmodule.modulestore import Location
from cms.djangoapps.models.settings.course_details import (CourseDetails,
CourseSettingsEncoder)
from cms.djangoapps.models.settings.course_grading import CourseGradingModel from cms.djangoapps.models.settings.course_grading import CourseGradingModel
from cms.djangoapps.contentstore.utils import get_modulestore from cms.djangoapps.contentstore.utils import get_modulestore
import copy
from django.test import TestCase
from utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from cms.djangoapps.models.settings.course_metadata import CourseMetadata from cms.djangoapps.models.settings.course_metadata import CourseMetadata
from xmodule.modulestore.xml_importer import import_from_xml from xmodule.modulestore.xml_importer import import_from_xml
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
# YYYY-MM-DDThh:mm:ss.s+/-HH:MM # YYYY-MM-DDThh:mm:ss.s+/-HH:MM
class ConvertersTestCase(TestCase): 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)
...@@ -39,8 +43,15 @@ class ConvertersTestCase(TestCase): ...@@ -39,8 +43,15 @@ class ConvertersTestCase(TestCase):
self.compare_dates(converters.jsdate_to_time("2013-01-01T00:00:00"), converters.jsdate_to_time("2012-12-31T23:59:59"), datetime.timedelta(seconds=1)) self.compare_dates(converters.jsdate_to_time("2013-01-01T00:00:00"), converters.jsdate_to_time("2012-12-31T23:59:59"), datetime.timedelta(seconds=1))
class CourseTestCase(TestCase): class CourseTestCase(ModuleStoreTestCase):
def setUp(self): def setUp(self):
"""
These tests need a user in the DB so that the django Test Client
can log them in.
They inherit from the ModuleStoreTestCase class so that the mongodb collection
will be cleared out before each test case execution and deleted
afterwards.
"""
uname = 'testuser' uname = 'testuser'
email = 'test+courses@edx.org' email = 'test+courses@edx.org'
password = 'foo' password = 'foo'
...@@ -55,36 +66,16 @@ class CourseTestCase(TestCase): ...@@ -55,36 +66,16 @@ class CourseTestCase(TestCase):
self.user.is_staff = True self.user.is_staff = True
self.user.save() self.user.save()
# Flush and initialize the module store
# It needs the templates because it creates new records
# by cloning from the template.
# Note that if your test module gets in some weird state
# (though it shouldn't), do this manually
# from the bash shell to drop it:
# $ mongo test_xmodule --eval "db.dropDatabase()"
xmodule.modulestore.django._MODULESTORES = {}
xmodule.modulestore.django.modulestore().collection.drop()
xmodule.templates.update_templates()
self.client = Client() self.client = Client()
self.client.login(username=uname, password=password) self.client.login(username=uname, password=password)
self.course_data = { t = 'i4x://edx/templates/course/Empty'
'template': 'i4x://edx/templates/course/Empty', o = 'MITx'
'org': 'MITx', n = '999'
'number': '999', dn = 'Robot Super Course'
'display_name': '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)
self.course_location = Location('i4x', 'MITx', '999', 'course', 'Robot_Super_Course')
self.create_course()
def tearDown(self):
xmodule.modulestore.django._MODULESTORES = {}
xmodule.modulestore.django.modulestore().collection.drop()
def create_course(self):
"""Create new course"""
self.client.post(reverse('create_new_course'), self.course_data)
class CourseDetailsTestCase(CourseTestCase): class CourseDetailsTestCase(CourseTestCase):
def test_virgin_fetch(self): def test_virgin_fetch(self):
...@@ -129,6 +120,7 @@ class CourseDetailsTestCase(CourseTestCase): ...@@ -129,6 +120,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)
...@@ -149,27 +141,22 @@ class CourseDetailsViewTest(CourseTestCase): ...@@ -149,27 +141,22 @@ class CourseDetailsViewTest(CourseTestCase):
else: else:
return None return None
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,
'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)
# 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")
...@@ -202,6 +189,7 @@ class CourseDetailsViewTest(CourseTestCase): ...@@ -202,6 +189,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)
...@@ -258,7 +246,7 @@ class CourseGradingTest(CourseTestCase): ...@@ -258,7 +246,7 @@ class CourseGradingTest(CourseTestCase):
altered_grader = CourseGradingModel.update_from_json(test_grader.__dict__) altered_grader = CourseGradingModel.update_from_json(test_grader.__dict__)
self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__, "cutoff add D") self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__, "cutoff add D")
test_grader.grace_period = {'hours' : '4'} test_grader.grace_period = {'hours' : 4, 'minutes' : 5, 'seconds': 0}
altered_grader = CourseGradingModel.update_from_json(test_grader.__dict__) altered_grader = CourseGradingModel.update_from_json(test_grader.__dict__)
self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__, "4 hour grace period") self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__, "4 hour grace period")
...@@ -275,7 +263,6 @@ class CourseGradingTest(CourseTestCase): ...@@ -275,7 +263,6 @@ class CourseGradingTest(CourseTestCase):
altered_grader = CourseGradingModel.update_grader_from_json(test_grader.course_location, test_grader.graders[1]) altered_grader = CourseGradingModel.update_grader_from_json(test_grader.course_location, test_grader.graders[1])
self.assertDictEqual(test_grader.graders[1], altered_grader, "drop_count[1] + 2") self.assertDictEqual(test_grader.graders[1], altered_grader, "drop_count[1] + 2")
class CourseMetadataEditingTest(CourseTestCase): class CourseMetadataEditingTest(CourseTestCase):
def setUp(self): def setUp(self):
CourseTestCase.setUp(self) CourseTestCase.setUp(self)
......
...@@ -2,27 +2,28 @@ from cms.djangoapps.contentstore.tests.test_course_settings import CourseTestCas ...@@ -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")
......
from django.test.testcases import TestCase
from cms.djangoapps.contentstore import utils from cms.djangoapps.contentstore import utils
import mock import mock
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")
......
import json
import copy
from time import time
from django.test import TestCase
from django.conf import settings
from student.models import Registration
from django.contrib.auth.models import User
import xmodule.modulestore.django
from xmodule.templates import update_templates
class ModuleStoreTestCase(TestCase):
""" Subclass for any test case that uses the mongodb
module store. This populates a uniquely named modulestore
collection with templates before running the TestCase
and drops it they are finished. """
def _pre_setup(self):
super(ModuleStoreTestCase, self)._pre_setup()
# Use the current seconds since epoch to differentiate
# the mongo collections on jenkins.
sec_since_epoch = '%s' % int(time() * 100)
self.orig_MODULESTORE = copy.deepcopy(settings.MODULESTORE)
self.test_MODULESTORE = self.orig_MODULESTORE
self.test_MODULESTORE['default']['OPTIONS']['collection'] = 'modulestore_%s' % sec_since_epoch
self.test_MODULESTORE['direct']['OPTIONS']['collection'] = 'modulestore_%s' % sec_since_epoch
settings.MODULESTORE = self.test_MODULESTORE
# Flush and initialize the module store
# It needs the templates because it creates new records
# by cloning from the template.
# Note that if your test module gets in some weird state
# (though it shouldn't), do this manually
# from the bash shell to drop it:
# $ mongo test_xmodule --eval "db.dropDatabase()"
xmodule.modulestore.django._MODULESTORES = {}
update_templates()
def _post_teardown(self):
# Make sure you flush out the modulestore.
# Drop the collection at the end of the test,
# otherwise there will be lingering collections leftover
# from executing the tests.
xmodule.modulestore.django._MODULESTORES = {}
xmodule.modulestore.django.modulestore().collection.drop()
settings.MODULESTORE = self.orig_MODULESTORE
super(ModuleStoreTestCase, self)._post_teardown()
def parse_json(response):
"""Parse response, which is assumed to be json"""
return json.loads(response.content)
def user(email):
"""look up a user by email"""
return User.objects.get(email=email)
def registration(email):
"""look up registration object by email"""
return Registration.objects.get(user__email=email)
...@@ -5,6 +5,7 @@ from xmodule.modulestore.exceptions import ItemNotFoundError ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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.
......
...@@ -47,12 +47,12 @@ class CourseGradingModel(object): ...@@ -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): ...@@ -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):
...@@ -155,7 +155,8 @@ class CourseGradingModel(object): ...@@ -155,7 +155,8 @@ class CourseGradingModel(object):
if 'grace_period' in graceperiodjson: if 'grace_period' in graceperiodjson:
graceperiodjson = graceperiodjson['grace_period'] graceperiodjson = graceperiodjson['grace_period']
grace_rep = " ".join(["%s %s" % (value, key) for (key, value) in graceperiodjson.iteritems()]) # lms requires these to be in a fixed order
grace_rep = "{0[hours]:d} hours {0[minutes]:d} minutes {0[seconds]:d} seconds".format(graceperiodjson)
descriptor = get_modulestore(course_location).get_item(course_location) descriptor = get_modulestore(course_location).get_item(course_location)
descriptor.metadata['graceperiod'] = grace_rep descriptor.metadata['graceperiod'] = grace_rep
...@@ -211,9 +212,9 @@ class CourseGradingModel(object): ...@@ -211,9 +212,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
...@@ -234,10 +235,10 @@ class CourseGradingModel(object): ...@@ -234,10 +235,10 @@ class CourseGradingModel(object):
@staticmethod @staticmethod
def convert_set_grace_period(descriptor): def convert_set_grace_period(descriptor):
# 5 hours 59 minutes 59 seconds => converted to iso format # 5 hours 59 minutes 59 seconds => { hours: 5, minutes : 59, seconds : 59}
rawgrace = descriptor.metadata.get('graceperiod', None) rawgrace = descriptor.metadata.get('graceperiod', None)
if rawgrace: if rawgrace:
parsedgrace = {str(key): val for (val, key) in re.findall('\s*(\d+)\s*(\w+)', rawgrace)} parsedgrace = {str(key): int(val) for (val, key) in re.findall('\s*(\d+)\s*(\w+)', rawgrace)}
return parsedgrace return parsedgrace
else: return None else: return None
...@@ -245,11 +246,11 @@ class CourseGradingModel(object): ...@@ -245,11 +246,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 = { ...@@ -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
...@@ -74,8 +74,8 @@ TEMPLATE_DIRS = MAKO_TEMPLATES['main'] ...@@ -74,8 +74,8 @@ TEMPLATE_DIRS = MAKO_TEMPLATES['main']
MITX_ROOT_URL = '' MITX_ROOT_URL = ''
LOGIN_REDIRECT_URL = MITX_ROOT_URL + '/login' LOGIN_REDIRECT_URL = MITX_ROOT_URL + '/signin'
LOGIN_URL = MITX_ROOT_URL + '/login' LOGIN_URL = MITX_ROOT_URL + '/signin'
TEMPLATE_CONTEXT_PROCESSORS = ( TEMPLATE_CONTEXT_PROCESSORS = (
...@@ -165,13 +165,6 @@ STATICFILES_DIRS = [ ...@@ -165,13 +165,6 @@ STATICFILES_DIRS = [
# This is how you would use the textbook images locally # This is how you would use the textbook images locally
# ("book", ENV_ROOT / "book_images") # ("book", ENV_ROOT / "book_images")
] ]
if os.path.isdir(GITHUB_REPO_ROOT):
STATICFILES_DIRS += [
# TODO (cpennington): When courses aren't loaded from github, remove this
(course_dir, GITHUB_REPO_ROOT / course_dir)
for course_dir in os.listdir(GITHUB_REPO_ROOT)
if os.path.isdir(GITHUB_REPO_ROOT / course_dir)
]
# Locale/Internationalization # Locale/Internationalization
TIME_ZONE = 'America/New_York' # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name TIME_ZONE = 'America/New_York' # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
...@@ -229,7 +222,7 @@ PIPELINE_JS = { ...@@ -229,7 +222,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 ...@@ -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 = { ...@@ -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 ...@@ -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
...@@ -11,7 +11,6 @@ from .common import * ...@@ -11,7 +11,6 @@ from .common import *
import os import os
from path import path from path import path
# Nose Test Runner # Nose Test Runner
INSTALLED_APPS += ('django_nose',) INSTALLED_APPS += ('django_nose',)
NOSE_ARGS = ['--with-xunit'] NOSE_ARGS = ['--with-xunit']
...@@ -63,7 +62,7 @@ CONTENTSTORE = { ...@@ -63,7 +62,7 @@ CONTENTSTORE = {
'ENGINE': 'xmodule.contentstore.mongo.MongoContentStore', 'ENGINE': 'xmodule.contentstore.mongo.MongoContentStore',
'OPTIONS': { 'OPTIONS': {
'host': 'localhost', 'host': 'localhost',
'db' : 'xcontent', 'db': 'xcontent',
} }
} }
...@@ -72,17 +71,6 @@ DATABASES = { ...@@ -72,17 +71,6 @@ DATABASES = {
'ENGINE': 'django.db.backends.sqlite3', 'ENGINE': 'django.db.backends.sqlite3',
'NAME': ENV_ROOT / "db" / "cms.db", 'NAME': ENV_ROOT / "db" / "cms.db",
}, },
# The following are for testing purposes...
'edX/toy/2012_Fall': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ENV_ROOT / "db" / "course1.db",
},
'edx/full/6.002_Spring_2012': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ENV_ROOT / "db" / "course2.db",
}
} }
LMS_BASE = "localhost:8000" LMS_BASE = "localhost:8000"
......
<li class="input input-existing multi course-grading-assignment-list-item"> <li class="field-group course-grading-assignment-list-item">
<div class="row row-col2"> <div class="field text" id="field-course-grading-assignment-name">
<label for="course-grading-assignment-name">Assignment Type Name:</label> <label for="course-grading-assignment-name">Assignment Type Name</label>
<input type="text" class="long" id="course-grading-assignment-name" value="<%= model.get('type') %>" />
<div class="field"> <span class="tip tip-stacked">e.g. Homework, Midterm Exams</span>
<div class="input course-grading-assignment-name">
<input type="text" class="long"
id="course-grading-assignment-name" value="<%= model.get('type') %>">
<span class="tip tip-stacked">e.g. Homework, Labs, Midterm Exams, Final Exam</span>
</div>
</div> </div>
</div>
<div class="row row-col2">
<label for="course-grading-shortname">Abbreviation:</label>
<div class="field"> <div class="field text" id="field-course-grading-assignment-shortname">
<div class="input course-grading-shortname"> <label for="course-grading-assignment-shortname">Abbreviation:</label>
<input type="text" class="short" <input type="text" class="short" id="course-grading-assignment-shortname" value="<%= model.get('short_label') %>" />
id="course-grading-assignment-shortname" <span class="tip tip-inline">e.g. HW, Midterm</span>
value="<%= model.get('short_label') %>">
<span class="tip tip-inline">e.g. HW, Midterm, Final</span>
</div>
</div>
</div> </div>
<div class="row row-col2"> <div class="field text" id="field-course-grading-assignment-gradeweight">
<label for="course-grading-gradeweight">Weight of Total <label for="course-grading-assignment-gradeweight">Weight of Total Grade</label>
Grade:</label> <input type="text" class="short" id="course-grading-assignment-gradeweight" value = "<%= model.get('weight') %>" />
<div class="field">
<div class="input course-grading-gradeweight">
<input type="text" class="short"
id="course-grading-assignment-gradeweight"
value = "<%= model.get('weight') %>">
<span class="tip tip-inline">e.g. 25%</span> <span class="tip tip-inline">e.g. 25%</span>
</div> </div>
</div>
</div>
<div class="row row-col2"> <div class="field text" id="field-course-grading-assignment-totalassignments">
<label for="course-grading-assignment-totalassignments">Total <label for="course-grading-assignment-totalassignments">Total
Number:</label> Number</label>
<input type="text" class="short" id="course-grading-assignment-totalassignments" value = "<%= model.get('min_count') %>" />
<div class="field">
<div class="input course-grading-totalassignments">
<input type="text" class="short"
id="course-grading-assignment-totalassignments"
value = "<%= model.get('min_count') %>">
<span class="tip tip-inline">total exercises assigned</span> <span class="tip tip-inline">total exercises assigned</span>
</div> </div>
</div>
</div>
<div class="row row-col2"> <div class="field text" id="field-course-grading-assignment-droppable">
<label for="course-grading-assignment-droppable">Number of <label for="course-grading-assignment-droppable">Number of
Droppable:</label> Droppable</label>
<input type="text" class="short" id="course-grading-assignment-droppable" value = "<%= model.get('drop_count') %>" />
<div class="field">
<div class="input course-grading-droppable">
<input type="text" class="short"
id="course-grading-assignment-droppable"
value = "<%= model.get('drop_count') %>">
<span class="tip tip-inline">total exercises that won't be graded</span> <span class="tip tip-inline">total exercises that won't be graded</span>
</div> </div>
<div class="actions">
<a href="#" class="button delete-button standard remove-item remove-grading-data"><span class="delete-icon"></span>Delete</a>
</div> </div>
</div>
<a href="#" class="delete-button standard remove-item remove-grading-data"><span class="delete-icon"></span>Delete</a>
</li> </li>
cms/static/img/html-icon.png

1.37 KB | W: | H:

cms/static/img/html-icon.png

581 Bytes | W: | H:

cms/static/img/html-icon.png
cms/static/img/html-icon.png
cms/static/img/html-icon.png
cms/static/img/html-icon.png
  • 2-up
  • Swipe
  • Onion skin
cms/static/img/large-discussion-icon.png

1.46 KB | W: | H:

cms/static/img/large-discussion-icon.png

737 Bytes | W: | H:

cms/static/img/large-discussion-icon.png
cms/static/img/large-discussion-icon.png
cms/static/img/large-discussion-icon.png
cms/static/img/large-discussion-icon.png
  • 2-up
  • Swipe
  • Onion skin
cms/static/img/large-freeform-icon.png

1.17 KB | W: | H:

cms/static/img/large-freeform-icon.png

412 Bytes | W: | H:

cms/static/img/large-freeform-icon.png
cms/static/img/large-freeform-icon.png
cms/static/img/large-freeform-icon.png
cms/static/img/large-freeform-icon.png
  • 2-up
  • Swipe
  • Onion skin
cms/static/img/large-problem-icon.png

1.49 KB | W: | H:

cms/static/img/large-problem-icon.png

797 Bytes | W: | H:

cms/static/img/large-problem-icon.png
cms/static/img/large-problem-icon.png
cms/static/img/large-problem-icon.png
cms/static/img/large-problem-icon.png
  • 2-up
  • Swipe
  • Onion skin
cms/static/img/large-video-icon.png

994 Bytes | W: | H:

cms/static/img/large-video-icon.png

234 Bytes | W: | H:

cms/static/img/large-video-icon.png
cms/static/img/large-video-icon.png
cms/static/img/large-video-icon.png
cms/static/img/large-video-icon.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -61,19 +61,17 @@ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({ ...@@ -61,19 +61,17 @@ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({
url: function() { url: function() {
var location = this.get('location'); var location = this.get('location');
return '/' + location.get('org') + "/" + location.get('course') + '/settings/' + location.get('name') + '/section/details'; return '/' + location.get('org') + "/" + location.get('course') + '/settings-details/' + location.get('name') + '/section/details';
}, },
_videokey_illegal_chars : /[^a-zA-Z0-9_-]/g, _videokey_illegal_chars : /[^a-zA-Z0-9_-]/g,
save_videosource: function(newsource) { save_videosource: function(newsource) {
// newsource either is <video youtube="speed:key, *"/> or just the "speed:key, *" string // newsource either is <video youtube="speed:key, *"/> or just the "speed:key, *" string
// returns the videosource for the preview which iss the key whose speed is closest to 1 // returns the videosource for the preview which iss the key whose speed is closest to 1
if (_.isEmpty(newsource) && !_.isEmpty(this.get('intro_video'))) this.save({'intro_video': null}, if (_.isEmpty(newsource) && !_.isEmpty(this.get('intro_video'))) this.set({'intro_video': null});
{ error : CMS.ServerError});
// TODO remove all whitespace w/in string // TODO remove all whitespace w/in string
else { else {
if (this.get('intro_video') !== newsource) this.save('intro_video', newsource, if (this.get('intro_video') !== newsource) this.set('intro_video', newsource);
{ error : CMS.ServerError});
} }
return this.videosourceSample(); return this.videosourceSample();
......
...@@ -13,7 +13,8 @@ CMS.Models.Settings.CourseGradingPolicy = Backbone.Model.extend({ ...@@ -13,7 +13,8 @@ CMS.Models.Settings.CourseGradingPolicy = Backbone.Model.extend({
} }
if (attributes['graders']) { if (attributes['graders']) {
var graderCollection; var graderCollection;
if (this.has('graders')) { // interesting race condition: if {parse:true} when newing, then parse called before .attributes created
if (this.attributes && this.has('graders')) {
graderCollection = this.get('graders'); graderCollection = this.get('graders');
graderCollection.reset(attributes.graders); graderCollection.reset(attributes.graders);
} }
...@@ -27,7 +28,7 @@ CMS.Models.Settings.CourseGradingPolicy = Backbone.Model.extend({ ...@@ -27,7 +28,7 @@ CMS.Models.Settings.CourseGradingPolicy = Backbone.Model.extend({
}, },
url : function() { url : function() {
var location = this.get('course_location'); var location = this.get('course_location');
return '/' + location.get('org') + "/" + location.get('course') + '/settings/' + location.get('name') + '/section/grading'; return '/' + location.get('org') + "/" + location.get('course') + '/settings-details/' + location.get('name') + '/section/grading';
}, },
gracePeriodToDate : function() { gracePeriodToDate : function() {
var newDate = new Date(); var newDate = new Date();
...@@ -119,7 +120,7 @@ CMS.Models.Settings.CourseGraderCollection = Backbone.Collection.extend({ ...@@ -119,7 +120,7 @@ CMS.Models.Settings.CourseGraderCollection = Backbone.Collection.extend({
model : CMS.Models.Settings.CourseGrader, model : CMS.Models.Settings.CourseGrader,
course_location : null, // must be set to a Location object course_location : null, // must be set to a Location object
url : function() { url : function() {
return '/' + this.course_location.get('org') + "/" + this.course_location.get('course') + '/grades/' + this.course_location.get('name') + '/'; return '/' + this.course_location.get('org') + "/" + this.course_location.get('course') + '/settings-grading/' + this.course_location.get('name') + '/';
}, },
sumWeights : function() { sumWeights : function() {
return this.reduce(function(subtotal, grader) { return subtotal + grader.get('weight'); }, 0); return this.reduce(function(subtotal, grader) { return subtotal + grader.get('weight'); }, 0);
......
...@@ -3,7 +3,6 @@ CMS.Models.Settings.CourseSettings = Backbone.Model.extend({ ...@@ -3,7 +3,6 @@ CMS.Models.Settings.CourseSettings = Backbone.Model.extend({
// a container for the models representing the n possible tabbed states // a container for the models representing the n possible tabbed states
defaults: { defaults: {
courseLocation: null, courseLocation: null,
// NOTE: keep these sync'd w/ the data-section names in settings-page-menu
details: null, details: null,
faculty: null, faculty: null,
grading: null, grading: null,
......
...@@ -59,6 +59,9 @@ $(document).ready(function() { ...@@ -59,6 +59,9 @@ $(document).ready(function() {
greedy: true greedy: true
}); });
// stop clicks on drag bars from doing their thing w/o stopping drag
$('.drag-handle').click(function(e) {e.preventDefault(); });
}); });
CMS.HesitateEvent.toggleXpandHesitation = null; CMS.HesitateEvent.toggleXpandHesitation = null;
...@@ -202,13 +205,17 @@ function _handleReorder(event, ui, parentIdField, childrenSelector) { ...@@ -202,13 +205,17 @@ function _handleReorder(event, ui, parentIdField, childrenSelector) {
children = _.without(children, ui.draggable.data('id')); children = _.without(children, ui.draggable.data('id'));
} }
// add to this parent (figure out where) // add to this parent (figure out where)
for (var i = 0; i < _els.length; i++) { for (var i = 0, bump = 0; i < _els.length; i++) {
if (!ui.draggable.is(_els[i]) && ui.offset.top < $(_els[i]).offset().top) { if (ui.draggable.is(_els[i])) {
bump = -1; // bump indicates that the draggable was passed in the dom but not children's list b/c
// it's not in that list
}
else if (ui.offset.top < $(_els[i]).offset().top) {
// insert at i in children and _els // insert at i in children and _els
ui.draggable.insertBefore($(_els[i])); ui.draggable.insertBefore($(_els[i]));
// TODO figure out correct way to have it remove the style: top:n; setting (and similar line below) // TODO figure out correct way to have it remove the style: top:n; setting (and similar line below)
ui.draggable.attr("style", "position:relative;"); ui.draggable.attr("style", "position:relative;");
children.splice(i, 0, ui.draggable.data('id')); children.splice(i + bump, 0, ui.draggable.data('id'));
break; break;
} }
} }
......
CMS.Views.ValidatingView = Backbone.View.extend({
// Intended as an abstract class which catches validation errors on the model and
// decorates the fields. Needs wiring per class, but this initialization shows how
// either have your init call this one or copy the contents
initialize : function() {
this.model.on('error', this.handleValidationError, this);
this.selectorToField = _.invert(this.fieldToSelectorMap);
},
errorTemplate : _.template('<span class="message-error"><%= message %></span>'),
events : {
"blur input" : "clearValidationErrors",
"blur textarea" : "clearValidationErrors"
},
fieldToSelectorMap : {
// Your subclass must populate this w/ all of the model keys and dom selectors
// which may be the subjects of validation errors
},
_cacheValidationErrors : [],
handleValidationError : function(model, error) {
// error triggered either by validation or server error
// error is object w/ fields and error strings
for (var field in error) {
var ele = this.$el.find('#' + this.fieldToSelectorMap[field]);
if (ele.length === 0) {
// check if it might a server error: note a typo in the field name
// or failure to put in a map may cause this to muffle validation errors
if (_.has(error, 'error') && _.has(error, 'responseText')) {
CMS.ServerError(model, error);
return;
}
else continue;
}
this._cacheValidationErrors.push(ele);
if ($(ele).is('div')) {
// put error on the contained inputs
$(ele).find('input, textarea').addClass('error');
}
else $(ele).addClass('error');
$(ele).parent().append(this.errorTemplate({message : error[field]}));
}
},
clearValidationErrors : function() {
// error is object w/ fields and error strings
while (this._cacheValidationErrors.length > 0) {
var ele = this._cacheValidationErrors.pop();
if ($(ele).is('div')) {
// put error on the contained inputs
$(ele).find('input, textarea').removeClass('error');
}
else $(ele).removeClass('error');
$(ele).nextAll('.message-error').remove();
}
},
saveIfChanged : function(event) {
// returns true if the value changed and was thus sent to server
var field = this.selectorToField[event.currentTarget.id];
var currentVal = this.model.get(field);
var newVal = $(event.currentTarget).val();
this.clearValidationErrors(); // curr = new if user reverts manually
if (currentVal != newVal) {
this.model.save(field, newVal);
return true;
}
else return false;
},
// these should perhaps go into a superclass but lack of event hash inheritance demotivates me
inputFocus : function(event) {
$("label[for='" + event.currentTarget.id + "']").addClass("is-focused");
},
inputUnfocus : function(event) {
$("label[for='" + event.currentTarget.id + "']").removeClass("is-focused");
}
});
// Studio - Sign In/Up
// ====================
body.signup, body.signin {
.wrapper-content {
margin: 0;
padding: 0 $baseline;
position: relative;
width: 100%;
}
.content {
@include clearfix();
@include font-size(16);
max-width: $fg-max-width;
min-width: $fg-min-width;
width: flex-grid(12);
margin: 0 auto;
color: $gray-d2;
header {
position: relative;
margin-bottom: $baseline;
border-bottom: 1px solid $gray-l4;
padding-bottom: ($baseline/2);
h1 {
@include font-size(32);
margin: 0;
padding: 0;
font-weight: 600;
}
.action {
@include font-size(13);
position: absolute;
right: 0;
top: 40%;
}
}
.introduction {
@include font-size(14);
margin: 0 0 $baseline 0;
}
}
.content-primary, .content-supplementary {
@include box-sizing(border-box);
float: left;
}
.content-primary {
width: flex-grid(8, 12);
margin-right: flex-gutter();
form {
@include box-sizing(border-box);
@include box-shadow(0 1px 2px $shadow-l1);
@include border-radius(2px);
width: 100%;
border: 1px solid $gray-l2;
padding: $baseline ($baseline*1.5);
background: $white;
.form-actions {
margin-top: $baseline;
.action-primary {
@include blue-button;
@include transition(all .15s);
@include font-size(15);
display:block;
width: 100%;
padding: ($baseline*0.75) ($baseline/2);
font-weight: 600;
text-transform: uppercase;
}
}
.list-input {
margin: 0;
padding: 0;
list-style: none;
.field {
margin: 0 0 ($baseline*0.75) 0;
&:last-child {
margin-bottom: 0;
}
&.required {
label {
font-weight: 600;
}
label:after {
margin-left: ($baseline/4);
content: "*";
}
}
label, input, textarea {
display: block;
}
label {
@include font-size(14);
@include transition(color, 0.15s, ease-in-out);
margin: 0 0 ($baseline/4) 0;
&.is-focused {
color: $blue;
}
}
input, textarea {
@include font-size(16);
height: 100%;
width: 100%;
padding: ($baseline/2);
&.long {
width: 100%;
}
&.short {
width: 25%;
}
::-webkit-input-placeholder {
color: $gray-l4;
}
:-moz-placeholder {
color: $gray-l3;
}
::-moz-placeholder {
color: $gray-l3;
}
:-ms-input-placeholder {
color: $gray-l3;
}
&:focus {
+ .tip {
color: $gray;
}
}
}
textarea.long {
height: ($baseline*5);
}
input[type="checkbox"] {
display: inline-block;
margin-right: ($baseline/4);
width: auto;
height: auto;
& + label {
display: inline-block;
}
}
.tip {
@include transition(color, 0.15s, ease-in-out);
@include font-size(13);
display: block;
margin-top: ($baseline/4);
color: $gray-l3;
}
}
.field-group {
@include clearfix();
margin: 0 0 ($baseline/2) 0;
.field {
display: block;
width: 47%;
border-bottom: none;
margin: 0 $baseline 0 0;
padding-bottom: 0;
&:nth-child(odd) {
float: left;
}
&:nth-child(even) {
float: right;
margin-right: 0;
}
input, textarea {
width: 100%;
}
}
}
}
}
}
.content-supplementary {
width: flex-grid(4, 12);
.bit {
@include font-size(13);
margin: 0 0 $baseline 0;
border-bottom: 1px solid $gray-l4;
padding: 0 0 $baseline 0;
color: $gray-l1;
&:last-child {
margin-bottom: 0;
border: none;
padding-bottom: 0;
}
h3 {
@include font-size(14);
margin: 0 0 ($baseline/4) 0;
color: $gray-d2;
font-weight: 600;
}
}
}
}
.signup {
}
.signin {
#field-password {
position: relative;
.action-forgotpassword {
@include font-size(13);
position: absolute;
top: 0;
right: 0;
}
}
}
// ====================
// messages
.message {
@include font-size(14);
display: block;
}
.message-status {
display: none;
@include border-top-radius(2px);
@include box-sizing(border-box);
border-bottom: 2px solid $yellow-d2;
margin: 0 0 $baseline 0;
padding: ($baseline/2) $baseline;
font-weight: 500;
background: $yellow-d1;
color: $white;
.ss-icon {
position: relative;
top: 3px;
@include font-size(16);
display: inline-block;
margin-right: ($baseline/2);
}
.text {
display: inline-block;
}
&.error {
border-color: shade($red, 50%);
background: tint($red, 20%);
}
&.is-shown {
display: block;
}
}
.assets { .uploads {
input.asset-search-input { input.asset-search-input {
float: left; float: left;
width: 260px; width: 260px;
......
// ------------------------------------- // studio base styling
// // ====================
// Universal
// // basic reset
// ------------------------------------- html {
font-size: 62.5%;
overflow-y: scroll;
}
body { body {
@include font-size(16);
min-width: 980px; min-width: 980px;
background: rgb(240, 241, 245); background: $gray-l5;
font-size: 16px;
line-height: 1.6; line-height: 1.6;
color: $baseFontColor; color: $baseFontColor;
} }
body, body, input {
input {
font-family: 'Open Sans', sans-serif; font-family: 'Open Sans', sans-serif;
} }
...@@ -28,10 +30,8 @@ a { ...@@ -28,10 +30,8 @@ a {
} }
h1 { h1 {
float: left; @include font-size(28);
font-size: 28px;
font-weight: 300; font-weight: 300;
margin: 24px 6px;
} }
.waiting { .waiting {
...@@ -42,6 +42,144 @@ h1 { ...@@ -42,6 +42,144 @@ h1 {
margin-bottom: 30px; margin-bottom: 30px;
} }
.wrapper {
@include clearfix();
@include box-sizing(border-box);
width: 100%;
}
// ====================
// layout - basic
.wrapper-content {
margin: 0;
padding: 0 $baseline;
position: relative;
}
.content {
@include clearfix();
@include font-size(16);
max-width: $fg-max-width;
min-width: $fg-min-width;
width: flex-grid(12);
margin: 0 auto;
color: $gray-d2;
header {
position: relative;
margin-bottom: $baseline;
border-bottom: 1px solid $gray-l4;
padding-bottom: ($baseline/2);
.title-sub {
@include font-size(14);
display: block;
margin: 0;
color: $gray-l2;
}
.title, .title-1 {
@include font-size(32);
margin: 0;
padding: 0;
font-weight: 600;
color: $gray-d3;
}
}
.introduction {
@include font-size(14);
margin: 0 0 $baseline 0;
}
}
.content-primary, .content-supplementary {
@include box-sizing(border-box);
}
.content-primary {
.title-1, .title-2, .title-3, .title-4, .title-5, .title-5 {
color: $gray-d3;
}
.title-1 {
}
.title-2 {
@include font-size(24);
margin: 0 0 ($baseline/2) 0;
font-weight: 600;
}
.title-3 {
@include font-size(16);
margin: 0 0 ($baseline/4) 0;
font-weight: 500;
}
.title-4 {
}
.title-5 {
}
}
.content-supplementary {
.bit {
@include font-size(13);
margin: 0 0 $baseline 0;
border-bottom: 1px solid $gray-l4;
padding: 0 0 $baseline 0;
color: $gray-l1;
&:last-child {
margin-bottom: 0;
border: none;
padding-bottom: 0;
}
h3 {
@include font-size(14);
margin: 0 0 ($baseline/4) 0;
color: $gray-d2;
font-weight: 600;
}
p {
margin: 0 0 $baseline 0;
&:last-child {
margin-bottom: 0;
}
}
.nav-related {
.nav-item {
margin-bottom: ($baseline/4);
border-bottom: 1px dotted $gray-l4;
padding-bottom: ($baseline/4);
&:last-child {
margin-bottom: 0;
border: none;
padding-bottom: 0;
}
}
}
}
}
// ====================
// layout - grandfathered
.main-wrapper { .main-wrapper {
position: relative; position: relative;
margin: 0 40px; margin: 0 40px;
...@@ -70,11 +208,9 @@ h1 { ...@@ -70,11 +208,9 @@ h1 {
float: right; float: right;
} }
footer { // ====================
clear: both;
height: 100px;
}
// forms
input[type="text"], input[type="text"],
input[type="email"], input[type="email"],
input[type="password"], input[type="password"],
...@@ -103,6 +239,7 @@ textarea.text { ...@@ -103,6 +239,7 @@ textarea.text {
} }
} }
// forms - specific
input.search { input.search {
padding: 6px 15px 8px 30px; padding: 6px 15px 8px 30px;
@include box-sizing(border-box); @include box-sizing(border-box);
...@@ -147,6 +284,21 @@ code { ...@@ -147,6 +284,21 @@ code {
font-family: Monaco, monospace; font-family: Monaco, monospace;
} }
// ====================
// UI - chrome
.window {
@include clearfix();
@include border-radius(3px);
@include box-shadow(0 1px 1px $shadow-l1);
margin-bottom: $baseline;
border: 1px solid $gray-l2;
background: $white;
}
// ====================
// UI - actions
.new-unit-item, .new-unit-item,
.new-subsection-item, .new-subsection-item,
.new-policy-item { .new-policy-item {
...@@ -177,6 +329,13 @@ code { ...@@ -177,6 +329,13 @@ code {
} }
} }
// ====================
// misc
hr.divide {
@include text-sr();
}
.item-details { .item-details {
float: left; float: left;
padding: 10px 0; padding: 10px 0;
...@@ -189,11 +348,11 @@ code { ...@@ -189,11 +348,11 @@ code {
} }
.window { .window {
margin-bottom: 20px; // @include border-radius(3px);
border: 1px solid $mediumGrey; // @include box-shadow(0 1px 1px $shadow-l1);
border-radius: 3px; // margin-bottom: $baseline;
background: #fff; // border: 1px solid $gray-l2;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.1)); // background: $white;
.window-contents { .window-contents {
padding: 20px; padding: 20px;
...@@ -236,34 +395,9 @@ code { ...@@ -236,34 +395,9 @@ code {
} }
} }
body.hide-wip { // ====================
.wip, .wip-box {
display: none !important;
}
}
body.show-wip {
.wip {
outline: 1px solid #f00 !important;
position: relative;
}
.wip-box { // system notifications
@extend .wip;
&:after {
content: "WIP";
font-size: 8px;
padding: 2px;
background: #f00;
color: #fff;
@include position(absolute, 0px 0px 0 0);
}
}
}
.waiting {
}
.toast-notification { .toast-notification {
display: none; display: none;
position: fixed; position: fixed;
...@@ -418,3 +552,47 @@ body.show-wip { ...@@ -418,3 +552,47 @@ body.show-wip {
color: rgba(0, 0, 0, 0.85); color: rgba(0, 0, 0, 0.85);
} }
} }
// ====================
// basic utility
.sr {
@include text-sr();
}
.fake-link {
cursor: pointer;
}
.non-list {
list-style: none;
margin: 0;
padding: 0;
}
.wrap {
text-wrap: wrap;
white-space: pre-wrap;
white-space: -moz-pre-wrap;
word-wrap: break-word;
}
// ====================
// works in progress
body.hide-wip {
.wip-box {
display: none;
}
}
// ====================
// needed fudges for now
body.dashboard {
.my-classes {
margin-top: $baseline;
}
}
\ No newline at end of file
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
} }
} }
&:hover { &:hover, &.active {
@include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset, 0 1px 1px rgba(0, 0, 0, .15)); @include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset, 0 1px 1px rgba(0, 0, 0, .15));
} }
} }
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
background-color: $blue; background-color: $blue;
color: #fff; color: #fff;
&:hover { &:hover, &.active {
background-color: #62aaf5; background-color: #62aaf5;
color: #fff; color: #fff;
} }
...@@ -286,3 +286,10 @@ ...@@ -286,3 +286,10 @@
position: absolute; position: absolute;
width: 1px; width: 1px;
} }
@mixin active {
@include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0));
background-color: rgba(255, 255, 255, .3);
@include box-shadow(0 -1px 0 rgba(0, 0, 0, .2) inset, 0 1px 0 #fff inset);
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
}
\ No newline at end of file
.faded-hr-divider {
@include background-image(linear-gradient(180deg, rgba(200,200,200, 0) 0%,
rgba(200,200,200, 1) 50%,
rgba(200,200,200, 0)));
height: 1px;
width: 100%;
}
.faded-hr-divider-medium {
@include background-image(linear-gradient(180deg, rgba(240,240,240, 0) 0%,
rgba(240,240,240, 1) 50%,
rgba(240,240,240, 0)));
height: 1px;
width: 100%;
}
.faded-hr-divider-light {
@include background-image(linear-gradient(180deg, rgba(255,255,255, 0) 0%,
rgba(255,255,255, 0.8) 50%,
rgba(255,255,255, 0)));
height: 1px;
width: 100%;
}
.faded-vertical-divider {
@include background-image(linear-gradient(90deg, rgba(200,200,200, 0) 0%,
rgba(200,200,200, 1) 50%,
rgba(200,200,200, 0)));
height: 100%;
width: 1px;
}
.faded-vertical-divider-light {
@include background-image(linear-gradient(90deg, rgba(255,255,255, 0) 0%,
rgba(255,255,255, 0.6) 50%,
rgba(255,255,255, 0)));
height: 100%;
width: 1px;
}
.vertical-divider {
@extend .faded-vertical-divider;
position: relative;
&::after {
@extend .faded-vertical-divider-light;
content: "";
display: block;
position: absolute;
left: 1px;
}
}
.horizontal-divider {
border: none;
@extend .faded-hr-divider;
position: relative;
&::after {
@extend .faded-hr-divider-light;
content: "";
display: block;
position: absolute;
top: 1px;
}
}
.fade-right-hr-divider {
@include background-image(linear-gradient(180deg, rgba(200,200,200, 0) 0%,
rgba(200,200,200, 1)));
border: none;
}
.fade-left-hr-divider {
@include background-image(linear-gradient(180deg, rgba(200,200,200, 1) 0%,
rgba(200,200,200, 0)));
border: none;
}
\ No newline at end of file
//studio global footer
.wrapper-footer {
margin: ($baseline*1.5) 0 $baseline 0;
padding: $baseline;
position: relative;
width: 100%;
footer.primary {
@include clearfix();
@include font-size(13);
max-width: $fg-max-width;
min-width: $fg-min-width;
width: flex-grid(12);
margin: 0 auto;
padding-top: $baseline;
border-top: 1px solid $gray-l4;
color: $gray-l2;
.colophon {
width: flex-grid(4, 12);
float: left;
margin-right: flex-gutter(2);
}
.nav-peripheral {
width: flex-grid(6, 12);
float: right;
text-align: right;
.nav-item {
display: inline-block;
margin-right: ($baseline/2);
&:last-child {
margin-right: 0;
}
}
}
a {
color: $gray-l1;
&:hover, &:active {
color: $blue;
}
}
}
}
\ No newline at end of file
...@@ -55,3 +55,15 @@ ...@@ -55,3 +55,15 @@
margin-top: 13px; margin-top: 13px;
} }
} }
// lean modal alternative
#lean_overlay {
position: fixed;
z-index: 10000;
top: 0px;
left: 0px;
display: none;
height: 100%;
width: 100%;
background: $black;
}
\ No newline at end of file
...@@ -55,3 +55,117 @@ table { ...@@ -55,3 +55,117 @@ table {
border-collapse: collapse; border-collapse: collapse;
border-spacing: 0; border-spacing: 0;
} }
/* Reset styles to remove ui-lightness jquery ui theme
from the tabs component (used in the add component problem tab menu)
*/
.ui-tabs {
padding: 0;
white-space: normal;
}
.ui-corner-all, .ui-corner-bottom, .ui-corner-left, ui-corner-top, .ui-corner-br, .ui-corner-right {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
border-top-right-radius: 0;
border-top-left-radius: 0;
}
.ui-widget-content {
border: 0;
background: none;
}
.ui-widget {
font-family: 'Open Sans', sans-serif;
font-size: 16px;
}
.ui-widget-header {
border:none;
background: none;
}
.ui-tabs .ui-tabs-nav {
padding: 0;
}
.ui-tabs .ui-tabs-nav li {
margin: 0;
padding: 0;
border: none;
top: 0;
margin: 0;
float: none;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.ui-tabs-nav {
li {
top: 0;
margin: 0;
}
a {
float: none;
font-weight: normal;
}
}
.ui-tabs .ui-tabs-panel {
padding: 0;
}
/* reapplying the tab styles from unit.scss after
removing jquery ui ui-lightness styling
*/
.problem-type-tabs {
border:none;
list-style-type: none;
width: 100%;
@include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0));
//background-color: $lightBluishGrey;
@include box-shadow(0 1px 0 rgba(255, 255, 255, 0.2) inset, 0 -1px 0 rgba(0, 0, 0, 0.2) inset);
li:first-child {
margin-left: 20px;
}
li {
opacity: .8;
&:ui-state-active {
background-color: rgba(255, 255, 255, .3);
opacity: 1;
font-weight: 400;
}
a:focus {
outline: none;
border: 0px;
}
}
/*
li {
float:left;
display:inline-block;
text-align:center;
width: auto;
//@include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0));
//background-color: tint($lightBluishGrey, 20%);
//@include box-shadow(0 1px 0 rgba(255, 255, 255, 0.2) inset, 0 -1px 0 rgba(0, 0, 0, 0.2) inset);
opacity:.8;
&:hover {
opacity:1;
}
&.current {
border: 0px;
//@include active;
opacity:1;
}
}
*/
}
\ No newline at end of file
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
&.new-component-item { &.new-component-item {
margin-top: 20px; margin-top: 20px;
background: transparent;
} }
} }
......
.subsection .main-wrapper {
margin: 40px;
}
.subsection .inner-wrapper {
@include clearfix();
}
.subsection-body { .subsection-body {
padding: 32px 40px; padding: 32px 40px;
@include clearfix; @include clearfix;
......
.unit .main-wrapper, .unit .main-wrapper {
.subsection .main-wrapper { @include clearfix();
margin: 40px; margin: 40px;
} }
//Problem Selector tab menu requirements
.js .tabs .tab {
display: none;
}
//end problem selector reqs
.main-column { .main-column {
clear: both; clear: both;
float: left; float: left;
...@@ -58,6 +64,7 @@ ...@@ -58,6 +64,7 @@
margin: 20px 40px; margin: 20px 40px;
.title { .title {
margin: 0 0 15px 0; margin: 0 0 15px 0;
color: $mediumGrey; color: $mediumGrey;
...@@ -67,22 +74,25 @@ ...@@ -67,22 +74,25 @@
} }
&.new-component-item { &.new-component-item {
padding: 20px; margin: 20px 0px;
border: none; border-top: 1px solid $mediumGrey;
border-radius: 3px; box-shadow: 0 2px 1px rgba(182, 182, 182, 0.75) inset;
background: $lightGrey; background-color: $lightGrey;
margin-bottom: 0px;
padding-bottom: 20px;
.new-component-button { .new-component-button {
display: block; display: block;
padding: 20px; padding: 20px;
text-align: center; text-align: center;
color: #6d788b; color: #edf1f5;
} }
h5 { h5 {
margin-bottom: 8px; margin: 20px 0px;
color: #fff; color: #fff;
font-weight: 700; font-weight: 600;
font-size: 18px;
} }
.rendered-component { .rendered-component {
...@@ -92,18 +102,21 @@ ...@@ -92,18 +102,21 @@
} }
.new-component-type { .new-component-type {
a, a,
li { li {
display: inline-block; display: inline-block;
} }
a { a {
border: 1px solid $mediumGrey;
width: 100px; width: 100px;
height: 100px; height: 100px;
margin-right: 10px; color: #fff;
margin-bottom: 10px; margin-right: 15px;
margin-bottom: 20px;
border-radius: 8px; border-radius: 8px;
font-size: 13px; font-size: 15px;
line-height: 14px; line-height: 14px;
text-align: center; text-align: center;
@include box-shadow(0 1px 1px rgba(0, 0, 0, .2), 0 1px 0 rgba(255, 255, 255, .4) inset); @include box-shadow(0 1px 1px rgba(0, 0, 0, .2), 0 1px 0 rgba(255, 255, 255, .4) inset);
...@@ -115,25 +128,40 @@ ...@@ -115,25 +128,40 @@
width: 100%; width: 100%;
padding: 10px; padding: 10px;
@include box-sizing(border-box); @include box-sizing(border-box);
color: #fff;
} }
} }
} }
.new-component-templates { .new-component-templates {
display: none; display: none;
padding: 20px; margin: 20px 40px 20px 40px;
border-radius: 3px;
border: 1px solid $mediumGrey;
background-color: #fff;
@include box-shadow(0 1px 1px rgba(0, 0, 0, .2), 0 1px 0 rgba(255, 255, 255, .4) inset);
@include clearfix; @include clearfix;
.cancel-button { .cancel-button {
margin: 20px 0px 10px 10px;
@include white-button; @include white-button;
} }
.problem-type-tabs {
display: none;
}
// specific menu types // specific menu types
&.new-component-problem { &.new-component-problem {
padding-bottom:10px;
.ss-icon, .editor-indicator { .ss-icon, .editor-indicator {
display: inline-block; display: inline-block;
} }
.problem-type-tabs {
display: inline-block;
}
} }
} }
...@@ -146,7 +174,6 @@ ...@@ -146,7 +174,6 @@
border: 1px solid $darkGreen; border: 1px solid $darkGreen;
background: tint($green,20%); background: tint($green,20%);
color: #fff; color: #fff;
@include transition(background-color .15s);
&:hover { &:hover {
background: $brightGreen; background: $brightGreen;
...@@ -154,19 +181,81 @@ ...@@ -154,19 +181,81 @@
} }
} }
.problem-type-tabs {
list-style-type: none;
border-radius: 0;
width: 100%;
@include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0));
background-color: $lightBluishGrey;
@include box-shadow(0 1px 0 rgba(255, 255, 255, 0.2) inset, 0 -1px 0 rgba(0, 0, 0, 0.2) inset);
li:first-child {
margin-left: 20px;
}
li {
float:left;
display:inline-block;
text-align:center;
width: auto;
@include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0));
background-color: tint($lightBluishGrey, 10%);
@include box-shadow(0 1px 0 rgba(255, 255, 255, 0.2) inset, 0 -1px 0 rgba(0, 0, 0, 0.2) inset);
opacity:.8;
&:hover {
opacity:1;
background-color: tint($lightBluishGrey, 20%);
}
&.ui-state-active {
border: 0px;
@include active;
opacity:1;
}
}
a{
display: block;
padding: 15px 25px;
font-size: 15px;
line-height: 16px;
text-align: center;
color: #3c3c3c;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.3);
}
}
.new-component-template { .new-component-template {
margin-bottom: 20px;
li:last-child {
a { a {
border-radius: 0 0 3px 3px; background: #fff;
border-bottom: 1px solid $darkGreen; border: 0px;
color: #3c3c3c;
@include transition (none);
&:hover {
background: tint($green,30%);
color: #fff;
@include transition(background-color .15s);
}
}
li {
border:none;
border-bottom: 1px dashed $lightGrey;
color: #fff;
}
li:first-child {
a {
border-top: 0px;
} }
} }
li:nth-child(2) { li:nth-child(2) {
a { a {
border-radius: 3px 3px 0 0; border-radius: 0px;
} }
} }
...@@ -175,18 +264,20 @@ ...@@ -175,18 +264,20 @@
display: block; display: block;
padding: 7px 20px; padding: 7px 20px;
border-bottom: none; border-bottom: none;
font-weight: 300; font-weight: 500;
.name { .name {
float: left; float: left;
.ss-icon { .ss-icon {
@include transition(opacity .15s); @include transition(opacity .15s);
position: relative; display: inline-block;
top: 1px; top: 1px;
font-size: 13px;
margin-right: 5px; margin-right: 5px;
opacity: 0.5; opacity: 0.5;
width: 17;
height: 21px;
vertical-align: middle;
} }
} }
...@@ -204,6 +295,7 @@ ...@@ -204,6 +295,7 @@
} }
&:hover { &:hover {
color: #fff;
.ss-icon { .ss-icon {
opacity: 1.0; opacity: 1.0;
...@@ -217,14 +309,18 @@ ...@@ -217,14 +309,18 @@
// specific editor types // specific editor types
.empty { .empty {
@include box-shadow(0 1px 3px rgba(0,0,0,0.2));
margin-bottom: 10px;
a { a {
border-bottom: 1px solid $darkGreen; line-height: 1.4;
border-radius: 3px; font-weight: 400;
font-weight: 500; background: #fff;
background: $green; color: #3c3c3c;
&:hover {
background: tint($green,30%);
color: #fff;
}
} }
} }
} }
...@@ -233,7 +329,7 @@ ...@@ -233,7 +329,7 @@
text-align: center; text-align: center;
h5 { h5 {
color: $green; color: $darkGreen;
} }
} }
...@@ -507,6 +603,7 @@ ...@@ -507,6 +603,7 @@
.edit-state-draft { .edit-state-draft {
.visibility, .visibility,
.edit-draft-message, .edit-draft-message,
.view-button { .view-button {
display: none; display: none;
......
$gw-column: 80px; $baseline: 20px;
$gw-gutter: 20px;
// grid
$gw-column: ($baseline*3);
$gw-gutter: $baseline;
$fg-column: $gw-column; $fg-column: $gw-column;
$fg-gutter: $gw-gutter; $fg-gutter: $gw-gutter;
$fg-max-columns: 12; $fg-max-columns: 12;
$fg-max-width: 1400px; $fg-max-width: 1280px;
$fg-min-width: 810px; $fg-min-width: 900px;
// type
$sans-serif: 'Open Sans', $verdana; $sans-serif: 'Open Sans', $verdana;
$body-line-height: golden-ratio(.875em, 1); $body-line-height: golden-ratio(.875em, 1);
$error-red: rgb(253, 87, 87);
$white: rgb(255,255,255); // colors - new for re-org
$black: rgb(0,0,0); $black: rgb(0,0,0);
$pink: rgb(182,37,104); $white: rgb(255,255,255);
$error-red: rgb(253, 87, 87);
$gray: rgb(127,127,127);
$gray-l1: tint($gray,20%);
$gray-l2: tint($gray,40%);
$gray-l3: tint($gray,60%);
$gray-l4: tint($gray,80%);
$gray-l5: tint($gray,90%);
$gray-d1: shade($gray,20%);
$gray-d2: shade($gray,40%);
$gray-d3: shade($gray,60%);
$gray-d4: shade($gray,80%);
$blue: rgb(85, 151, 221);
$blue-l1: tint($blue,20%);
$blue-l2: tint($blue,40%);
$blue-l3: tint($blue,60%);
$blue-l4: tint($blue,80%);
$blue-l5: tint($blue,90%);
$blue-d1: shade($blue,20%);
$blue-d2: shade($blue,40%);
$blue-d3: shade($blue,60%);
$blue-d4: shade($blue,80%);
$pink: rgb(183, 37, 103);
$pink-l1: tint($pink,20%);
$pink-l2: tint($pink,40%);
$pink-l3: tint($pink,60%);
$pink-l4: tint($pink,80%);
$pink-l5: tint($pink,90%);
$pink-d1: shade($pink,20%);
$pink-d2: shade($pink,40%);
$pink-d3: shade($pink,60%);
$pink-d4: shade($pink,80%);
$green: rgb(37, 184, 90);
$green-l1: tint($green,20%);
$green-l2: tint($green,40%);
$green-l3: tint($green,60%);
$green-l4: tint($green,80%);
$green-l5: tint($green,90%);
$green-d1: shade($green,20%);
$green-d2: shade($green,40%);
$green-d3: shade($green,60%);
$green-d4: shade($green,80%);
$yellow: rgb(231, 214, 143);
$yellow-l1: tint($yellow,20%);
$yellow-l2: tint($yellow,40%);
$yellow-l3: tint($yellow,60%);
$yellow-l4: tint($yellow,80%);
$yellow-l5: tint($yellow,90%);
$yellow-d1: shade($yellow,20%);
$yellow-d2: shade($yellow,40%);
$yellow-d3: shade($yellow,60%);
$yellow-d4: shade($yellow,80%);
$shadow: rgba(0,0,0,0.2);
$shadow-l1: rgba(0,0,0,0.1);
$shadow-d1: rgba(0,0,0,0.4);
// colors - inherited
$baseFontColor: #3c3c3c; $baseFontColor: #3c3c3c;
$offBlack: #3c3c3c; $offBlack: #3c3c3c;
$black: rgb(0,0,0);
$white: rgb(255,255,255);
$blue: #5597dd;
$orange: #edbd3c; $orange: #edbd3c;
$red: #b20610; $red: #b20610;
$green: #108614; $green: #108614;
......
@import 'bourbon/bourbon'; @import 'bourbon/bourbon';
@import 'bourbon/addons/button';
@import 'vendor/normalize'; @import 'vendor/normalize';
@import 'keyframes'; @import 'keyframes';
...@@ -8,8 +9,10 @@ ...@@ -8,8 +9,10 @@
@import "fonts"; @import "fonts";
@import "variables"; @import "variables";
@import "cms_mixins"; @import "cms_mixins";
@import "extends";
@import "base"; @import "base";
@import "header"; @import "header";
@import "footer";
@import "dashboard"; @import "dashboard";
@import "courseware"; @import "courseware";
@import "subsection"; @import "subsection";
...@@ -26,6 +29,8 @@ ...@@ -26,6 +29,8 @@
@import "modal"; @import "modal";
@import "alerts"; @import "alerts";
@import "login"; @import "login";
@import "account";
@import "index";
@import 'jquery-ui-calendar'; @import 'jquery-ui-calendar';
@import 'content-types'; @import 'content-types';
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<section class="activation"> <section class="activation">
<h1>Account already active!</h1> <h1>Account already active!</h1>
<p> This account has already been activated. <a href="/login">Log in here</a>.</p> <p> This account has already been activated. <a href="/signin">Log in here</a>.</p>
</div> </div>
</section> </section>
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
<section class="tos"> <section class="tos">
<div> <div>
<h1>Activation Complete!</h1> <h1>Activation Complete!</h1>
<p>Thanks for activating your account. <a href="/login">Log in here</a>.</p> <p>Thanks for activating your account. <a href="/signin">Log in here</a>.</p>
</div> </div>
</section> </section>
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="bodyclass">assets</%block> <%block name="bodyclass">is-signedin course uploads</%block>
<%block name="title">Courseware Assets</%block> <%block name="title">Uploads &amp; Files</%block>
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
......
...@@ -5,23 +5,29 @@ ...@@ -5,23 +5,29 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>
<%block name="title"></%block> |
% if context_course:
<% ctx_loc = context_course.location %>
${context_course.display_name} |
% endif
edX Studio
</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="path_prefix" content="${MITX_ROOT_URL}">
<%static:css group='base-style'/> <%static:css group='base-style'/>
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/skins/simple/style.css')}" /> <link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/skins/simple/style.css')}" />
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/sets/wiki/style.css')}" /> <link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/sets/wiki/style.css')}" />
<link rel="stylesheet" type="text/css" href="${static.url('css/vendor/symbolset.ss-standard.css')}" />
<link rel="stylesheet" type="text/css" href="${static.url('css/vendor/symbolset.ss-symbolicons-block.css')}" /> <link rel="stylesheet" type="text/css" href="${static.url('css/vendor/symbolset.ss-symbolicons-block.css')}" />
<link rel="stylesheet" type="text/css" href="${static.url('css/vendor/symbolset.ss-standard.css')}" />
<title><%block name="title"></%block></title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="path_prefix" content="${MITX_ROOT_URL}">
<%block name="header_extras"></%block> <%block name="header_extras"></%block>
</head> </head>
<body class="<%block name='bodyclass'></%block> hide-wip"> <body class="<%block name='bodyclass'></%block> hide-wip">
<%include file="widgets/header.html" args="active_tab=active_tab"/> <%include file="widgets/header.html" />
<%include file="courseware_vendor_js.html"/> <%include file="courseware_vendor_js.html"/>
<script type="text/javascript" src="${static.url('js/vendor/json2.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/json2.js')}"></script>
...@@ -47,9 +53,9 @@ ...@@ -47,9 +53,9 @@
</script> </script>
<%block name="content"></%block> <%block name="content"></%block>
<%include file="widgets/footer.html" />
<%block name="jsextra"></%block> <%block name="jsextra"></%block>
</body> </body>
</html> </html>
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%block name="title">Course Manager</%block>
<%include file="widgets/header.html"/> <%include file="widgets/header.html"/>
<%block name="content"> <%block name="content">
......
...@@ -2,8 +2,9 @@ ...@@ -2,8 +2,9 @@
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<!-- TODO decode course # from context_course into title --> <!-- TODO decode course # from context_course into title -->
<%block name="title">Course Info</%block> <%block name="title">Updates</%block>
<%block name="bodyclass">course-info</%block> <%block name="bodyclass">is-signedin course course-info updates</%block>
<%block name="jsextra"> <%block name="jsextra">
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script> <script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">Edit Static Page</%block> <%block name="title">Editing Static Page</%block>
<%block name="bodyclass">edit-static-page</%block> <%block name="bodyclass">is-signedin course pages edit-static-page</%block>
<%block name="content"> <%block name="content">
<div class="main-wrapper"> <div class="main-wrapper">
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">Tabs</%block> <%block name="title">Static Pages</%block>
<%block name="bodyclass">static-pages</%block> <%block name="bodyclass">is-signedin course pages static-pages</%block>
<%block name="jsextra"> <%block name="jsextra">
<script type='text/javascript'> <script type='text/javascript'>
......
...@@ -7,8 +7,9 @@ ...@@ -7,8 +7,9 @@
%> %>
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="bodyclass">subsection</%block>
<%block name="title">CMS Subsection</%block> <%block name="title">CMS Subsection</%block>
<%block name="bodyclass">is-signedin course subsection</%block>
<%namespace name="units" file="widgets/units.html" /> <%namespace name="units" file="widgets/units.html" />
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
...@@ -97,6 +98,7 @@ ...@@ -97,6 +98,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</%block> </%block>
<%block name="jsextra"> <%block name="jsextra">
...@@ -107,6 +109,8 @@ ...@@ -107,6 +109,8 @@
<script type="text/javascript" src="${static.url('js/models/course_relative.js')}"></script> <script type="text/javascript" src="${static.url('js/models/course_relative.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/grader-select-view.js')}"></script> <script type="text/javascript" src="${static.url('js/views/grader-select-view.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/settings/course_grading_policy.js')}"></script> <script type="text/javascript" src="${static.url('js/models/settings/course_grading_policy.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/overview.js')}"></script>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(document).ready(function() {
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">Export</%block> <%block name="title">Export Course</%block>
<%block name="bodyclass">export</%block> <%block name="bodyclass">is-signedin course tools export</%block>
<%block name="content"> <%block name="content">
<div class="main-wrapper"> <div class="main-wrapper">
......
<%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %>
<%block name="title">Welcome</%block>
<%block name="bodyclass">not-signedin index howitworks</%block>
<%block name="content">
<div class="wrapper-content-header wrapper">
<section class="content content-header">
<header>
<h1>Welcome to <span class="logo">edX Studio</span></h1>
<p class="tagline">Studio helps manage your courses online, so you can focus on teaching them</p>
</header>
</section>
</div>
<div class="wrapper-content-features wrapper">
<section class="content content-features">
<header>
<h2 class="sr">Studio's Many Features</h2>
</header>
<ol class="list-features">
<li class="feature">
<figure class="img zoom">
<a rel="modal" href="#hiw-feature1">
<img src="/static/img/thumb-hiw-feature1.png" alt="Studio Helps You Keep Your Courses Organized" />
<figcaption class="sr">Studio Helps You Keep Your Courses Organized</figcaption>
<span class="action-zoom">
<i class="ss-icon ss-symbolicons-block">&#xE002;</i>
</span>
</a>
</figure>
<div class="copy">
<h3>Keeping Your Course Organized</h3>
<p>The backbone of your course is how it is organized. Studio offers an <strong>Outline</strong> editor, providing a simple hierarchy and easy drag and drop to help you and your students stay organized.</p>
<ul class="list-proofpoints">
<li class="proofpoint">
<h4 class="title">Simple Organization For Content</h4>
<p>Studio uses a simple hierarchy of <strong>sections</strong> and <strong>subsections</strong> to organize your content.</p>
</li>
<li class="proofpoint">
<h4 class="title">Change Your Mind Anytime</h4>
<p>Draft your outline and build content anywhere. Simple drag and drop tools let your reorganize quickly.</p>
</li>
<li class="proofpoint">
<h4 class="title">Go A Week Or A Semester At A Time</h4>
<p>Build and release <strong>sections</strong> to your students incrementally. You don't have to have it all done at once.</p>
</li>
</ul>
</div>
</li>
<li class="feature">
<figure class="img zoom">
<a rel="modal" href="#hiw-feature2">
<img src="/static/img/thumb-hiw-feature2.png" alt="Learning is More than Just Lectures" />
<figcaption class="sr">Learning is More than Just Lectures</figcaption>
<span class="action-zoom">
<i class="ss-icon ss-symbolicons-block">&#xE002;</i>
</span>
</a>
</figure>
<div class="copy">
<h3>Learning is More than Just Lectures</h3>
<p>Studio lets you weave your content together in a way that reinforces learning &mdash; short video lectures interleaved with exercises and more. Insert videos and author a wide variety of exercise types with just a few clicks. </p>
<ul class="list-proofpoints">
<li class="proofpoint">
<h4 class="title">Create Learning Pathways</h4>
<p>Help your students understand a small interactive piece at a time with multimedia, HTML, and exercises.</p>
</li>
<li class="proofpoint">
<h4 class="title">Work Visually, Organize Quickly</h4>
<p>Work visually and see exactly what your students will see. Reorganize all your content with drag and drop.</p>
</li>
<li class="proofpoint">
<h4 class="title">A Broad Library of Problem Types</h4>
<p>It's more than just multiple choice. Studio has nearly a dozen types of problems to challenge your learners.</p>
</li>
</ul>
</div>
</li>
<li class="feature">
<figure class="img zoom">
<a rel="modal" href="#hiw-feature3">
<img src="/static/img/thumb-hiw-feature3.png" alt="Studio Gives You Simple, Fast, and Incremental Publishing. With Friends." />
<figcaption class="sr">Studio Gives You Simple, Fast, and Incremental Publishing. With Friends.</figcaption>
<span class="action-zoom">
<i class="ss-icon ss-symbolicons-block">&#xE002;</i>
</span>
</a>
</figure>
<div class="copy">
<h3>Simple, Fast, and Incremental Publishing. With Friends.</h3>
<p>Studio works like web applications you already know, yet understands how you build curriculum. Instant publishing to the web when you want it, incremental release when it makes sense. And with co-authors, you can have a whole team building a course, together.</p>
<ul class="list-proofpoints">
<li class="proofpoint">
<h4 class="title">Instant Changes</h4>
<p>Caught a bug? No problem. When you want, your changes to live when you hit Save.</p>
</li>
<li class="proofpoint">
<h4 class="title">Release-On Date Publishing</h4>
<p>When you've finished a <strong>section</strong>, pick when you want it to go live and Studio takes care of the rest. Build your course incrementally.</p>
</li>
<li class="proofpoint">
<h4 class="title">Work in Teams</h4>
<p>Co-authors have full access to all the same authoring tools. Make your course better through a team effort.</p>
</li>
</ul>
</div>
</li>
</ol>
</section>
</div>
<div class="wrapper-content-cta wrapper">
<section class="content content-cta">
<header>
<h2 class="sr">Sign Up for Studio Today!</h2>
</header>
<ul class="list-actions">
<li>
<a href="${reverse('signup')}" class="action action-primary">Sign Up &amp; Start Making an edX Course</a>
</li>
<li>
<a href="${reverse('login')}" class="action action-secondary">Already have a Studio Account? Sign In</a>
</li>
</ul>
</section>
</div>
<div class="content-modal" id="hiw-feature1">
<h3 class="title">Outlining Your Course</h3>
<figure>
<img src="/static/img/hiw-feature1.png" alt="" />
<figcaption class="description">Simple two-level outline to organize your couse. Drag and drop, and see your course at a glance.</figcaption>
</figure>
<a href="#" rel="view" class="action action-modal-close">
<i class="ss-icon ss-symbolicons-block">&#x2421;</i>
<span class="label">close modal</span>
</a>
</div>
<div class="content-modal" id="hiw-feature2">
<h3 class="title">More than Just Lectures</h3>
<figure>
<img src="/static/img/hiw-feature2.png" alt="" />
<figcaption class="description">Quickly create videos, text snippets, inline discussions, and a variety of problem types.</figcaption>
</figure>
<a href="#" rel="view" class="action action-modal-close">
<i class="ss-icon ss-symbolicons-block">&#x2421;</i>
<span class="label">close modal</span>
</a>
</div>
<div class="content-modal" id="hiw-feature3">
<h3 class="title">Publishing on Date</h3>
<figure>
<img src="/static/img/hiw-feature3.png" alt="" />
<figcaption class="description">Simply set the date of a section or subsection, and Studio will publish it to your students for you.</figcaption>
</figure>
<a href="#" rel="view" class="action action-modal-close">
<i class="ss-icon ss-symbolicons-block">&#x2421;</i>
<span class="label">close modal</span>
</a>
</div>
</%block>
<%block name="jsextra">
<script type="text/javascript">
(function() {
// lean modal window
$('a[rel*=modal]').leanModal({overlay : 0.50, closeButton: '.action-modal-close' });
$('a.action-modal-close').click(function(e){
(e).preventDefault();
});
})(this)
</script>
</%block>
\ No newline at end of file
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">Import</%block> <%block name="title">Import Course</%block>
<%block name="bodyclass">import</%block> <%block name="bodyclass">is-signedin course tools import</%block>
<%block name="content"> <%block name="content">
<div class="main-wrapper"> <div class="main-wrapper">
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%block name="bodyclass">index</%block>
<%block name="title">Courses</%block> <%block name="title">Courses</%block>
<%block name="bodyclass">is-signedin index dashboard</%block>
<%block name="header_extras"> <%block name="header_extras">
<script type="text/template" id="new-course-template"> <script type="text/template" id="new-course-template">
...@@ -37,7 +38,9 @@ ...@@ -37,7 +38,9 @@
<h1>My Courses</h1> <h1>My Courses</h1>
<article class="my-classes"> <article class="my-classes">
% if user.is_active: % if user.is_active:
% if not disable_course_creation:
<a href="#" class="new-button new-course-button"><span class="plus-icon white"></span> New Course</a> <a href="#" class="new-button new-course-button"><span class="plus-icon white"></span> New Course</a>
%endif
<ul class="class-list"> <ul class="class-list">
%for course, url in courses: %for course, url in courses:
<li> <li>
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">Log in</%block> <%block name="title">Sign In</%block>
<%block name="bodyclass">no-header</%block> <%block name="bodyclass">not-signedin signin</%block>
<%block name="content"> <%block name="content">
<div class="edx-studio-logo-large"></div> <div class="wrapper-content wrapper">
<section class="content">
<article class="log-in-box">
<header> <header>
<h1>Log in to edX studio</h1> <h1 class="title title-1">Sign In to edX Studio</h1>
<a href="${reverse('signup')}" class="action action-signin">Don't have a Studio Account? Sign up!</a>
</header> </header>
<form class="log-in-form" id="login_form" action="login_post" method="post">
<div class="row"> <article class="content-primary" role="main">
<label>Email</label> <form id="login_form" method="post" action="login_post">
<input name="email" type="email" class="email-field" tabindex="1">
</div> <fieldset>
<div class="row"> <legend class="sr">Required Information to Sign In to edX Studio</legend>
<label>Password <a href="${forgot_password_link}" class="forgot-button">Forgot password?</a></label>
<input name="password" type="password" class="password-field" tabindex="2"> <ol class="list-input">
</div> <li class="field text required" id="field-email">
<div class="row form-actions"> <label for="email">Email Address</label>
<input name="submit" type="submit" value="Log In" class="log-in-button" tabindex="3"> <input id="email" type="email" name="email" placeholder="e.g. jane.doe@gmail.com" />
<span class="or">or</span> </li>
<a href="${reverse('signup')}" class="sign-up-button" tabindex="4">Sign up</a>
<li class="field text required" id="field-password">
<a href="${forgot_password_link}" class="action action-forgotpassword" tabindex="-1">Forgot password?</a>
<label for="password">Password</label>
<input id="password" type="password" name="password" />
</li>
</ol>
</fieldset>
<div class="form-actions">
<button type="submit" id="submit" name="submit" class="action action-primary">Sign In to edX Studio</button>
</div> </div>
<!-- no honor code for CMS, but need it because we're using the lms student object -->
<input name="honor_code" type="checkbox" value="true" checked="true" hidden="true">
</form> </form>
</article> </article>
<aside class="content-supplementary" role="complimentary">
<h2 class="sr">Studio Support</h2>
<div class="bit">
<h3 class="title-3">Need Help?</h3>
<p>Having trouble with your account? Use <a href="http://help.edge.edx.org" rel="external">our support center</a> to look over self help steps, find solutions others have found to the same problem, or let us know of your issue.</p>
</div>
</aside>
</section>
</div>
</%block>
<%block name="jsextra">
<script type="text/javascript"> <script type="text/javascript">
(function() { (function() {
function getCookie(name) { function getCookie(name) {
...@@ -51,12 +77,16 @@ ...@@ -51,12 +77,16 @@
submit_data, submit_data,
function(json) { function(json) {
if(json.success) { if(json.success) {
location.href = "${reverse('index')}"; var next = /next=([^&]*)/g.exec(decodeURIComponent(window.location.search));
if (next && next.length > 1) {
location.href = next[1];
}
else location.href = "${reverse('homepage')}";
} else if($('#login_error').length == 0) { } else if($('#login_error').length == 0) {
$('#login_form').prepend('<div id="login_error">' + json.value + '</div>'); $('#login_form').prepend('<div id="login_error" class="message message-status error">' + json.value + '</span></div>');
$('#login_error').slideDown(150); $('#login_error').addClass('is-shown');
} else { } else {
$('#login_error').stop().slideDown(150); $('#login_error').stop().addClass('is-shown');
$('#login_error').html(json.value); $('#login_error').html(json.value);
} }
} }
...@@ -64,5 +94,4 @@ ...@@ -64,5 +94,4 @@
}); });
})(this) })(this)
</script> </script>
</%block> </%block>
\ No newline at end of file
<%inherit file="base.html" /> <%inherit file="base.html" />
<%block name="title">Course Staff Manager</%block> <%block name="title">Course Staff Manager</%block>
<%block name="bodyclass">users</%block> <%block name="bodyclass">is-signedin course users settings team</%block>
<%block name="content"> <%block name="content">
<div class="main-wrapper"> <div class="main-wrapper">
...@@ -97,7 +98,7 @@ ...@@ -97,7 +98,7 @@
$cancelButton.bind('click', hideNewUserForm); $cancelButton.bind('click', hideNewUserForm);
$('.new-user-button').bind('click', showNewUserForm); $('.new-user-button').bind('click', showNewUserForm);
$body.bind('keyup', { $cancelButton: $cancelButton }, checkForCancel); $('body').bind('keyup', { $cancelButton: $cancelButton }, checkForCancel);
$('.remove-user').click(function() { $('.remove-user').click(function() {
$.ajax({ $.ajax({
......
...@@ -6,7 +6,8 @@ ...@@ -6,7 +6,8 @@
from datetime import datetime from datetime import datetime
%> %>
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">CMS Courseware Overview</%block> <%block name="title">Course Outline</%block>
<%block name="bodyclass">is-signedin course outline</%block>
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<%namespace name="units" file="widgets/units.html" /> <%namespace name="units" file="widgets/units.html" />
......
<%inherit file="base.html" />
<%block name="title">Grading</%block>
<%block name="bodyclass">is-signedin course grading settings</%block>
<%namespace name='static' file='static_content.html'/>
<%!
from contentstore import utils
%>
<%block name="jsextra">
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" />
<script src="${static.url('js/vendor/timepicker/jquery.timepicker.js')}"></script>
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/course_relative.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/validating_view.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/settings/course_grading_policy.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/settings/settings_grading_view.js')}"></script>
<script type="text/javascript">
$(document).ready(function(){
$("form :input").focus(function() {
$("label[for='" + this.id + "']").addClass("is-focused");
}).blur(function() {
$("label").removeClass("is-focused");
});
var editor = new CMS.Views.Settings.Grading({
el: $('.settings-grading'),
model : new CMS.Models.Settings.CourseGradingPolicy(${course_details|n},{parse:true})
});
editor.render();
});
</script>
</%block>
<%block name="content">
<div class="wrapper-content wrapper">
<section class="content">
<header class="page">
<span class="title-sub">Settings</span>
<h1 class="title-1">Grading</h1>
</header>
<!-- <div class="introduction">
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.</p>
</div> -->
<article class="content-primary" role="main">
<form id="settings_details" class="settings-grading" method="post" action="">
<section class="group-settings grade-range">
<header>
<h2 class="title-2">Overall Grade Range</h2>
<span class="tip">Your overall grading scale for student final grades</span>
</header>
<ol class="list-input">
<li class="field" id="field-course-grading-range">
<div class="grade-controls course-grading-range well">
<a href="#" class="new-grade-button"><span class="plus-icon"></span></a>
<div class="grade-slider">
<div class="grade-bar">
<ol class="increments">
<li class="increment-0">0</li>
<li class="increment-10">10</li>
<li class="increment-20">20</li>
<li class="increment-30">30</li>
<li class="increment-40">40</li>
<li class="increment-50">50</li>
<li class="increment-60">60</li>
<li class="increment-70">70</li>
<li class="increment-80">80</li>
<li class="increment-90">90</li>
<li class="increment-100">100</li>
</ol>
<ol class="grades">
</ol>
</div>
</div>
</div>
</li>
</ol>
</section>
<hr class="divide" />
<section class="group-settings grade-rules">
<header>
<h2 class="title-2">Grading Rules &amp; Policies</h2>
<span class="tip">Deadlines, requirements, and logistics around grading student work</span>
</header>
<ol class="list-input">
<li class="field text" id="field-course-grading-graceperiod">
<label for="course-grading-graceperiod">Grace Period on Deadline:</label>
<input type="text" class="short time" id="course-grading-graceperiod" value="0:00" placeholder="e.g. 10 minutes">
<span class="tip tip-inline">Leeway on due dates</span>
</li>
</ol>
</section>
<hr class="divide" />
<section class="group-settings assignment-types">
<header>
<h2 class="title-2">Assignment Types</h2>
<span class="tip">Categories and labels for any exercises that are gradable</span>
</header>
<ol class="list-input course-grading-assignment-list enum">
</ol>
<div class="actions">
<a href="#" class="new-button new-course-grading-item add-grading-data">
<span class="plus-icon white"></span>New Assignment Type
</a>
</div>
</section>
</form>
</article>
<aside class="content-supplementary" role="complimentary">
<div class="bit">
<h3 class="title-3">How will these settings be used</h3>
<p>Your grading settings will be used to calculate students grades and performance.</p>
<p>Overall grade range will be used in students' final grades, which are calculated by the weighting you determine for each custom assignment type.</p>
</div>
<div class="bit">
% if context_course:
<% ctx_loc = context_course.location %>
<%! from django.core.urlresolvers import reverse %>
<h3 class="title-3">Other Course Settings</h3>
<nav class="nav-related">
<ul>
<li class="nav-item"><a href="${reverse('contentstore.views.get_course_settings', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Details &amp; Schedule</a></li>
<li class="nav-item"><a href="${reverse('manage_users', kwargs=dict(location=ctx_loc))}">Course Team</a></li>
</ul>
</nav>
% endif
</div>
</aside>
</section>
</div>
</%block>
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="title">Sign up</%block> <%block name="title">Sign Up</%block>
<%block name="bodyclass">no-header</%block> <%block name="bodyclass">not-signedin signup</%block>
<%block name="content"> <%block name="content">
<div class="edx-studio-logo-large"></div> <div class="wrapper-content wrapper">
<section class="content">
<article class="sign-up-box">
<header> <header>
<h1>Register for edX studio</h1> <h1 class="title title-1">Sign Up for edX Studio</h1>
<a href="${reverse('login')}" class="action action-signin">Already have a Studio Account? Sign in</a>
</header> </header>
<form id="register_form" method="post">
<div id="register_error" name="register_error"></div> <p class="introduction">Ready to start creating online courses? Sign up below and start creating your first edX course today.</p>
<div class="row">
<label>Email</label> <article class="content-primary" role="main">
<input name="email" type="email"> <form id="register_form" method="post" action="register_post">
</div> <div id="register_error" name="register_error" class="message message-status message-status error">
<div class="row">
<label>Password</label>
<input name="password" type="password">
</div>
<div class="row">
<label>Public Username</label>
<input name="username" type="text">
</div>
<div class="row">
<label>Full Name</label>
<input name="name" type="text">
</div>
<div class="row">
<div class="split">
<label>Your Location</label>
<input name="location" type="text">
</div> </div>
<div class="split">
<label>Preferred Language</label> <fieldset>
<input name="language" type="text"> <legend class="sr">Required Information to Sign Up for edX Studio</legend>
<ol class="list-input">
<li class="field text required" id="field-email">
<label for="email">Email Address</label>
<input id="email" type="email" name="email" placeholder="e.g. jane.doe@gmail.com" />
</li>
<li class="field text required" id="field-password">
<label for="password">Password</label>
<input id="password" type="password" name="password" />
</li>
<li class="field text required" id="field-username">
<label for="username">Public Username</label>
<input id="username" type="text" name="username" placeholder="e.g. janedoe" />
<span class="tip tip-stacked">This will be used in public discussions with your courses and in our edX101 support forums</span>
</li>
<li class="field text required" id="field-name">
<label for="name">Full Name</label>
<input id="name" type="text" name="name" placeholder="e.g. Jane Doe" />
</li>
<li class="field-group">
<div class="field text" id="field-location">
<label for="location">Your Location</label>
<input class="short" id="location" type="text" name="location" />
</div> </div>
<div class="field text" id="field-language">
<label for="language">Preferred Language</label>
<input class="short" id="language" type="text" name="language" />
</div> </div>
<div class="row"> </li>
<label class="terms-of-service">
<input name="terms_of_service" type="checkbox" value="true"> <li class="field checkbox required" id="field-tos">
I agree to the <input id="tos" name="terms_of_service" type="checkbox" value="true" />
<a href="#">Terms of Service</a> <label for="tos">I agree to the Terms of Service</label>
</label> </li>
</ol>
</fieldset>
<div class="form-actions">
<button type="submit" id="submit" name="submit" class="action action-primary">Create My Account & Start Authoring Courses</button>
</div> </div>
<!-- no honor code for CMS, but need it because we're using the lms student object --> <!-- no honor code for CMS, but need it because we're using the lms student object -->
<input name="honor_code" type="checkbox" value="true" checked="true" hidden="true"> <input name="honor_code" type="checkbox" value="true" checked="true" hidden="true">
</form>
</article>
<aside class="content-supplementary" role="complimentary">
<h2 class="sr">Common Studio Questions</h2>
<div class="row form-actions submit"> <div class="bit">
<input name="submit" type="submit" value="Create My Account" class="create-account-button"> <h3 class="title-3">Who is Studio for?</h3>
<p class="enrolled">Already enrolled? <a href="/">Log In.</a></p> <p>Studio is for anyone that wants to create online courses that leverage the global edX platform. Our users are often faculty members, teaching assistants and course staff, and members of instructional technology groups.</p>
</div>
<div class="bit">
<h3 class="title-3">How technically savvy do I need to be to create courses in Studio?</h3>
<p>Studio is designed to be easy to use by almost anyone familiar with common web-based authoring environments (Wordpress, Moodle, etc.). No programming knowledge is required, but for some of the more advanced features, a technical background would be helpful. As always, we are here to help, so don't hesitate to dive right in.</p>
</div> </div>
</form>
</article>
<script type="text/javascript"> <div class="bit">
<h3 class="title-3">I've never authored a course online before. Is there help?</h3>
<p>Absolutely. We have created an online course, edX101, that describes some best practices: from filming video, creating exercises, to the basics of running an online course. Additionally, we're always here to help, just drop us a note.</p>
</div>
</aside>
</section>
</div>
</%block>
<%block name="jsextra">
<script type="text/javascript">
(function() { (function() {
$("form :input").focus(function() {
$("label[for='" + this.id + "']").addClass("is-focused");
}).blur(function() {
$("label").removeClass("is-focused");
});
function getCookie(name) { function getCookie(name) {
return $.cookie(name); return $.cookie(name);
} }
// form validation
function postJSON(url, data, callback) { function postJSON(url, data, callback) {
$.ajax({type:'POST', $.ajax({type:'POST',
url: url, url: url,
...@@ -84,11 +131,11 @@ ...@@ -84,11 +131,11 @@
if(json.success) { if(json.success) {
location.href = "${reverse('index')}"; location.href = "${reverse('index')}";
} else { } else {
$('#register_error').html(json.value).stop().slideDown(150); $('#register_error').html(json.value).stop().addClass('is-shown');
} }
} }
); );
}); });
})(this) })(this)
</script> </script>
</%block> </%block>
\ No newline at end of file
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%namespace name="units" file="widgets/units.html" /> <%namespace name="units" file="widgets/units.html" />
<%block name="bodyclass">unit</%block> <%block name="title">Individual Unit</%block>
<%block name="title">CMS Unit</%block> <%block name="bodyclass">is-signedin course unit</%block>
<%block name="jsextra"> <%block name="jsextra">
<script type='text/javascript'> <script type='text/javascript'>
$(document).ready(function() { $(document).ready(function() {
...@@ -14,11 +15,19 @@ ...@@ -14,11 +15,19 @@
}) })
}); });
$(document).ready(function() {
$('body').addClass('js');
// tabs
$('.tab-group').tabs();
});
$('.new-component-template').each(function(){ $('.new-component-template').each(function(){
$emptyEditor = $(this).find('.empty'); $emptyEditor = $(this).find('.empty');
$(this).prepend($emptyEditor); $(this).prepend($emptyEditor);
}); });
}); });
</script> </script>
</%block> </%block>
...@@ -56,38 +65,66 @@ ...@@ -56,38 +65,66 @@
</div> </div>
% for type, templates in sorted(component_templates.items()): % for type, templates in sorted(component_templates.items()):
<div class="new-component-templates new-component-${type}"> <div class="new-component-templates new-component-${type}">
<h3 class="title">Select <span class="type">${type}</span> component type:</h3> % if type == "problem":
<div class="tab-group tabs">
<ul class="problem-type-tabs nav-tabs">
<li class="current">
<a class="link-tab" href="#tab1">Common Problem Types</a>
</li>
<li>
<a class="link-tab" href="#tab2">Advanced</a>
</li>
</ul>
% endif
<div class="tab current" id="tab1">
<ul class="new-component-template"> <ul class="new-component-template">
% for name, location, has_markdown, is_empty in templates: % for name, location, has_markdown, is_empty in templates:
% if has_markdown or type != "problem":
% if is_empty: % if is_empty:
<li class="editor-md empty"> <li class="editor-md empty">
<a href="#" data-location="${location}"> <a href="#" data-location="${location}">
<span class="name"><i class="ss-icon ss-symbolicons-block">&#xE714;</i> ${name}</span> <span class="name"> ${name}</span>
<span class="editor-indicator">Simple <span class="sr">Editor</span></span>
</a> </a>
</li> </li>
% elif has_markdown: % else:
<li class="editor-md"> <li class="editor-md">
<a href="#" data-location="${location}"> <a href="#" data-location="${location}">
<span class="name"><i class="ss-icon ss-symbolicons-block">&#xE714;</i> ${name}</span> <span class="name"> ${name}</span>
<span class="editor-indicator">Simple <span class="sr">Editor</span></span> </a>
</li>
% endif
% endif
%endfor
</ul>
</div>
% if type == "problem":
<div class="tab" id="tab2">
<ul class="new-component-template">
% for name, location, has_markdown, is_empty in templates:
% if not has_markdown:
% if is_empty:
<li class="editor-manual empty">
<a href="#" data-location="${location}">
<span class="name">${name}</span>
</a> </a>
</li> </li>
% else: % else:
<li class="editor-manual"> <li class="editor-manual">
<a href="#" data-location="${location}"> <a href="#" data-location="${location}">
<span class="name"><i class="ss-icon ss-symbolicons-block">&#x1F527;</i> ${name}</span> <span class="name"> ${name}</span>
<span class="editor-indicator">Advanced <span class="sr">Editor</span></span>
</a> </a>
</li> </li>
% endif % endif
% endif
%endfor % endfor
</ul> </ul>
</div>
</div>
% endif
<a href="#" class="cancel-button">Cancel</a> <a href="#" class="cancel-button">Cancel</a>
</div> </div>
% endfor % endfor
......
<%! from django.core.urlresolvers import reverse %>
<div class="wrapper-footer wrapper">
<footer class="primary" role="contentinfo">
<div class="colophon">
<p>&copy; 2013 <a href="http://www.edx.org" rel="external">edX</a>. All rights reserved.</p>
</div>
<nav class="nav-peripheral">
<ol>
<!-- <li class="nav-item nav-peripheral-tos">
<a href="#">Terms of Service</a>
</li>
<li class="nav-item nav-peripheral-pp">
<a href="#">Privacy Policy</a>
</li> -->
<li class="nav-item nav-peripheral-help">
<a href="http://help.edge.edx.org/" rel="external">edX Studio Help</a>
</li>
<li class="nav-item nav-peripheral-contact">
<a href="https://www.edx.org/contact" rel="external">Contact edX</a>
</li>
% if user.is_authenticated():
<!-- add in zendesk/tender feedback form UI -->
% endif
</ol>
</nav>
</footer>
</div>
\ No newline at end of file
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<% active_tab_class = 'active-tab-' + active_tab if active_tab else '' %> <div class="wrapper-header wrapper">
<header class="primary-header ${active_tab_class}"> <header class="primary" role="banner">
<div class="class">
<div class="inner-wrapper"> <div class="wrapper wrapper-left ">
<div class="left"> <h1 class="branding"><a href="/">edX Studio</a></h1>
% if context_course: % if context_course:
<% ctx_loc = context_course.location %> <% ctx_loc = context_course.location %>
<a href="/" class="home"><span class="small-home-icon"></span></a> <div class="info-course">
<a href="${reverse('course_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" class="class-name">${context_course.display_name}</a> <h2 class="sr">Current Course:</h2>
% endif <a href="${reverse('course_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">
<span class="course-org">${ctx_loc.org}</span><span class="course-number">${ctx_loc.course}</span>
<span class="course-title" title="${context_course.display_name}">${context_course.display_name}</span>
</a>
</div> </div>
<div class="right"> <nav class="nav-course primary nav-dropdown" role="navigation">
<span class="username">${ user.username }</span> <h2 class="sr">PH207x's Navigation:</h2>
% if user.is_authenticated():
<a href="${reverse('logout')}" class="log-out"><span class="log-out-icon"></span></a> <ol>
% else: <li class="nav-item nav-course-courseware">
<a href="${reverse('login')}">Log in</a> <h3 class="title"><span class="label-prefix">Course </span>Content <i class="ss-icon ss-symbolicons-block icon-expand">&#x25BE;</i></h3>
% endif
<div class="wrapper wrapper-nav-sub">
<div class="nav-sub">
<ul>
<li class="nav-item nav-course-courseware-outline"><a href="${reverse('course_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Outline</a></li>
<li class="nav-item nav-course-courseware-updates"><a href="${reverse('course_info', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Updates</a></li>
<li class="nav-item nav-course-courseware-pages"><a href="${reverse('edit_tabs', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, coursename=ctx_loc.name))}">Static Pages</a></li>
<li class="nav-item nav-course-courseware-uploads"><a href="${reverse('asset_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Files &amp; Uploads</a></li>
</ul>
</div>
</div> </div>
</li>
<li class="nav-item nav-course-settings">
<h3 class="title"><span class="label-prefix">Course </span>Settings <i class="ss-icon ss-symbolicons-block icon-expand">&#x25BE;</i></h3>
<div class="wrapper wrapper-nav-sub">
<div class="nav-sub">
<ul>
<li class="nav-item nav-course-settings-schedule"><a href="${reverse('contentstore.views.get_course_settings', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Schedule &amp; Details</a></li>
<li class="nav-item nav-course-settings-grading"><a href="${reverse('contentstore.views.course_config_graders_page', kwargs={'org' : ctx_loc.org, 'course' : ctx_loc.course, 'name': ctx_loc.name})}">Grading</a></li>
<li class="nav-item nav-course-settings-team"><a href="${reverse('manage_users', kwargs=dict(location=ctx_loc))}">Course Team</a></li>
<!-- <li class="nav-item nav-course-settings-advanced"><a href="${reverse('course_settings', kwargs={'org' : ctx_loc.org, 'course' : ctx_loc.course, 'name': ctx_loc.name})}">Advanced Settings</a></li> -->
</ul>
</div> </div>
</div> </div>
<nav class="class-nav-bar"> </li>
% if context_course:
<% ctx_loc = context_course.location %> <li class="nav-item nav-course-tools">
<ul class="class-nav inner-wrapper"> <h3 class="title">Tools <i class="ss-icon ss-symbolicons-block icon-expand">&#x25BE;</i></h3>
<li><a href="${reverse('course_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='courseware-tab'>Courseware</a></li>
<li><a href="${reverse('course_info', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='courseinfo-tab'>Course Info</a></li> <div class="wrapper wrapper-nav-sub">
<li><a href="${reverse('edit_tabs', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, coursename=ctx_loc.name))}" id='pages-tab'>Pages</a></li> <div class="nav-sub">
<li><a href="${reverse('manage_users', kwargs=dict(location=ctx_loc))}" id='users-tab'>Users</a></li> <ul>
<li><a href="${reverse('asset_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='assets-tab'>Assets</a></li> <li class="nav-item nav-course-tools-import"><a href="${reverse('import_course', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Import</a></li>
<li><a href="${reverse('course_settings', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='settings-tab'>Settings</a></li> <li class="nav-item nav-course-tools-export"><a href="${reverse('export_course', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Export</a></li>
<li><a href="${reverse('import_course', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='import-tab'>Import</a></li>
<li><a href="${reverse('export_course', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='export-tab'>Export</a></li>
</ul> </ul>
</div>
</div>
</li>
</ol>
</nav>
% endif % endif
</div>
<div class="wrapper wrapper-right">
% if user.is_authenticated():
<nav class="nav-account nav-is-signedin nav-dropdown">
<h2 class="sr">Currently logged in as:</h2>
<ol>
<li class="nav-item nav-account-username">
<a href="#" class="title">
<span class="account-username">
<i class="ss-icon ss-symbolicons-standard icon-user">&#x1F464;</i>
${ user.username }
</span>
<i class="ss-icon ss-symbolicons-block icon-expand">&#x25BE;</i>
</a>
<div class="wrapper wrapper-nav-sub">
<div class="nav-sub">
<ul>
<li class="nav-item nav-account-dashboard"><a href="/">My Courses</a></li>
<li class="nav-item nav-account-help"><a href="http://help.edge.edx.org/" rel="external">Studio Help</a></li>
<li class="nav-item nav-account-signout"><a class="action action-signout" href="${reverse('logout')}">Sign Out</a></li>
</ul>
</div>
</div>
</li>
</ol>
</nav> </nav>
</header> % else:
<nav class="nav-not-signedin">
<h2 class="sr">You're not currently signed in</h2>
<ol>
<li class="nav-item nav-not-signedin-hiw">
<a href="/">How Studio Works</a>
</li>
<li class="nav-item nav-not-signedin-help">
<a href="http://help.edge.edx.org/" rel="external">Studio Help</a>
</li>
<li class="nav-item nav-not-signedin-signup">
<a class="action action-signup" href="${reverse('signup')}">Sign Up</a>
</li>
<li class="nav-item nav-not-signedin-signin">
<a class="action action-signin" href="${reverse('login')}">Sign In</a>
</li>
</ol>
</nav>
% endif
</div>
</header>
</div>
\ No newline at end of file
<%include file="metadata-edit.html" /> <%include file="metadata-edit.html" />
<section class="problem-editor editor"> <section class="problem-editor editor">
<div class="row"> <div class="row">
%if markdown != '' or data == '<problem>\n</problem>\n': %if enable_markdown:
<div class="editor-bar"> <div class="editor-bar">
<ul class="format-buttons"> <ul class="format-buttons">
<li><a href="#" class="header-button" data-tooltip="Heading 1"><span <li><a href="#" class="header-button" data-tooltip="Heading 1"><span
class="problem-editor-icon heading1"></span></a></li> class="problem-editor-icon heading1"></span></a></li>
<li><a href="#" class="multiple-choice-button" data-tooltip="Multiple Choice"><span <li><a href="#" class="multiple-choice-button" data-tooltip="Multiple Choice"><span
class="problem-editor-icon multiple-choice"></span></a></li> class="problem-editor-icon multiple-choice"></span></a></li>
<li><a href="#" class="checks-button" data-tooltip="Check Multiple"><span <li><a href="#" class="checks-button" data-tooltip="Checkboxes"><span
class="problem-editor-icon checks"></span></a></li> class="problem-editor-icon checks"></span></a></li>
<li><a href="#" class="string-button" data-tooltip="String Response"><span <li><a href="#" class="string-button" data-tooltip="Text Input"><span
class="problem-editor-icon string"></span></a></li> class="problem-editor-icon string"></span></a></li>
<li><a href="#" class="number-button" data-tooltip="Numerical Response"><span <li><a href="#" class="number-button" data-tooltip="Numerical Input"><span
class="problem-editor-icon number"></span></a></li> class="problem-editor-icon number"></span></a></li>
<li><a href="#" class="dropdown-button" data-tooltip="Option Response"><span <li><a href="#" class="dropdown-button" data-tooltip="Dropdown"><span
class="problem-editor-icon dropdown"></span></a></li> class="problem-editor-icon dropdown"></span></a></li>
<li><a href="#" class="explanation-button" data-tooltip="Explanation"><span <li><a href="#" class="explanation-button" data-tooltip="Explanation"><span
class="problem-editor-icon explanation"></span></a></li> class="problem-editor-icon explanation"></span></a></li>
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<h6>Check Multiple</h6> <h6>Checkboxes</h6>
<div class="col sample check-multiple"> <div class="col sample check-multiple">
<img src="/static/img/multi-example.png" /> <img src="/static/img/multi-example.png" />
</div> </div>
...@@ -67,7 +67,7 @@ ...@@ -67,7 +67,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<h6>String Response</h6> <h6>Text Input</h6>
<div class="col sample string-response"> <div class="col sample string-response">
<img src="/static/img/string-example.png" /> <img src="/static/img/string-example.png" />
</div> </div>
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<h6>Numerical Response</h6> <h6>Numerical Input</h6>
<div class="col sample numerical-response"> <div class="col sample numerical-response">
<img src="/static/img/number-example.png" /> <img src="/static/img/number-example.png" />
</div> </div>
...@@ -85,7 +85,7 @@ ...@@ -85,7 +85,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<h6>Option Response</h6> <h6>Dropdown</h6>
<div class="col sample option-reponse"> <div class="col sample option-reponse">
<img src="/static/img/select-example.png" /> <img src="/static/img/select-example.png" />
</div> </div>
......
...@@ -6,7 +6,8 @@ from django.conf.urls import patterns, include, url ...@@ -6,7 +6,8 @@ from django.conf.urls import patterns, include, url
# admin.autodiscover() # admin.autodiscover()
urlpatterns = ('', urlpatterns = ('',
url(r'^$', 'contentstore.views.index', name='index'), url(r'^$', 'contentstore.views.howitworks', name='homepage'),
url(r'^listing', 'contentstore.views.index', name='index'),
url(r'^edit/(?P<location>.*?)$', 'contentstore.views.edit_unit', name='edit_unit'), url(r'^edit/(?P<location>.*?)$', 'contentstore.views.edit_unit', name='edit_unit'),
url(r'^subsection/(?P<location>.*?)$', 'contentstore.views.edit_subsection', name='edit_subsection'), url(r'^subsection/(?P<location>.*?)$', 'contentstore.views.edit_subsection', name='edit_subsection'),
url(r'^preview_component/(?P<location>.*?)$', 'contentstore.views.preview_component', name='preview_component'), url(r'^preview_component/(?P<location>.*?)$', 'contentstore.views.preview_component', name='preview_component'),
...@@ -42,10 +43,10 @@ urlpatterns = ('', ...@@ -42,10 +43,10 @@ urlpatterns = ('',
'contentstore.views.remove_user', name='remove_user'), 'contentstore.views.remove_user', name='remove_user'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/info/(?P<name>[^/]+)$', 'contentstore.views.course_info', name='course_info'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/info/(?P<name>[^/]+)$', 'contentstore.views.course_info', name='course_info'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course_info/updates/(?P<provided_id>.*)$', 'contentstore.views.course_info_updates', name='course_info'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course_info/updates/(?P<provided_id>.*)$', 'contentstore.views.course_info_updates', name='course_info'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings/(?P<name>[^/]+)$', 'contentstore.views.get_course_settings', name='course_settings'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings-details/(?P<name>[^/]+)$', 'contentstore.views.get_course_settings', name='course_settings'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings/(?P<name>[^/]+)/section/(?P<section>[^/]+).*$', 'contentstore.views.course_settings_updates', name='course_settings'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings-grading/(?P<name>[^/]+)$', 'contentstore.views.course_config_graders_page', name='course_settings'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/grades/(?P<name>[^/]+)/(?P<grader_index>.*)$', 'contentstore.views.course_grader_updates', name='course_settings'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings-details/(?P<name>[^/]+)/section/(?P<section>[^/]+).*$', 'contentstore.views.course_settings_updates', name='course_settings'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/advanced/(?P<name>[^/]+).*$', 'contentstore.views.course_metadata_rest_access', name='course_advanced_settings'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings-grading/(?P<name>[^/]+)/(?P<grader_index>.*)$', 'contentstore.views.course_grader_updates', name='course_settings'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/(?P<category>[^/]+)/(?P<name>[^/]+)/gradeas.*$', 'contentstore.views.assignment_type_update', name='assignment_type_update'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/(?P<category>[^/]+)/(?P<name>[^/]+)/gradeas.*$', 'contentstore.views.assignment_type_update', name='assignment_type_update'),
...@@ -77,13 +78,15 @@ urlpatterns = ('', ...@@ -77,13 +78,15 @@ urlpatterns = ('',
# User creation and updating views # User creation and updating views
urlpatterns += ( urlpatterns += (
url(r'^howitworks$', 'contentstore.views.howitworks', name='howitworks'),
url(r'^signup$', 'contentstore.views.signup', name='signup'), url(r'^signup$', 'contentstore.views.signup', name='signup'),
url(r'^create_account$', 'student.views.create_account'), url(r'^create_account$', 'student.views.create_account'),
url(r'^activate/(?P<key>[^/]*)$', 'student.views.activate_account', name='activate'), url(r'^activate/(?P<key>[^/]*)$', 'student.views.activate_account', name='activate'),
# form page # form page
url(r'^login$', 'contentstore.views.login_page', name='login'), url(r'^login$', 'contentstore.views.old_login_redirect', name='old_login'),
url(r'^signin$', 'contentstore.views.login_page', name='login'),
# ajax view that actually does the work # ajax view that actually does the work
url(r'^login_post$', 'student.views.login_user', name='login_post'), url(r'^login_post$', 'student.views.login_user', name='login_post'),
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment