Commit 4d57f1b9 by Vik Paruchuri

Merge remote-tracking branch 'origin/master' into feature/vik/settings-to-xmodule

parents 7a3a56d4 f4addddf
...@@ -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
......
...@@ -20,7 +20,8 @@ def i_visit_the_studio_homepage(step): ...@@ -20,7 +20,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$')
...@@ -113,7 +114,11 @@ def log_into_studio( ...@@ -113,7 +114,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)
...@@ -127,17 +132,19 @@ def create_a_course(): ...@@ -127,17 +132,19 @@ 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'
css_click(link_css) css_click(link_css)
name_css = '.new-section-name' name_css = 'input.new-section-name'
save_css = '.new-section-name-save' save_css = 'input.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'
......
...@@ -34,8 +34,8 @@ def i_click_the_course_link_in_my_courses(step): ...@@ -34,8 +34,8 @@ def i_click_the_course_link_in_my_courses(step):
@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$')
......
...@@ -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."
\ No newline at end of file
...@@ -11,10 +11,11 @@ def i_fill_in_the_registration_form(step): ...@@ -11,10 +11,11 @@ def i_fill_in_the_registration_form(step):
register_form.find_by_name('terms_of_service').check() register_form.find_by_name('terms_of_service').check()
@step('I press the "([^"]*)" button on the registration form$') @step('I press the Create My Account button on the registration form$')
def i_press_the_button_on_the_registration_form(step, button): def i_press_the_button_on_the_registration_form(step):
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$')
......
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:
data = replace_static_urls( data = replace_static_urls(
module.definition['data'], module.definition['data'],
None,
course_namespace=Location([
module.location.tag,
module.location.org,
module.location.course,
None, None,
None course_namespace=Location([
]) module.location.tag,
) module.location.org,
module.location.course,
None,
None
])
)
return { return {
'id': module.location.url(), 'id': module.location.url(),
'data': data, 'data': data,
'metadata': module.metadata 'metadata': module.metadata
...@@ -39,58 +37,56 @@ def get_module_info(store, location, parent_location=None, rewrite_static_links= ...@@ -39,58 +37,56 @@ def get_module_info(store, location, parent_location=None, rewrite_static_links=
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) else:
else: module = store.get_item(location)
module = store.get_item(location) except:
except: pass
pass
if module is None: if module is None:
# new module at this location # new module at this location
# 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']
store.update_item(location, data) store.update_item(location, data)
# cdodge: note calling request.POST.get('children') will return None if children is an empty array # cdodge: note calling request.POST.get('children') will return None if children is an empty array
# so it lead to a bug whereby the last component to be deleted in the UI was not actually # so it lead to a bug whereby the last component to be deleted in the UI was not actually
# deleting the children object from the children collection # deleting the children object from the children collection
if 'children' in post_data and post_data['children'] is not None: if 'children' in post_data and post_data['children'] is not None:
children = post_data['children'] children = post_data['children']
store.update_children(location, children) store.update_children(location, children)
# cdodge: also commit any metadata which might have been passed along in the # cdodge: also commit any metadata which might have been passed along in the
# POST from the client, if it is there # POST from the client, if it is there
# NOTE, that the postback is not the complete metadata, as there's system metadata which is # NOTE, that the postback is not the complete metadata, as there's system metadata which is
# not presented to the end-user for editing. So let's fetch the original and # not presented to the end-user for editing. So let's fetch the original and
# 'apply' the submitted metadata, so we don't end up deleting system metadata # 'apply' the submitted metadata, so we don't end up deleting system metadata
if post_data.get('metadata') is not None: if post_data.get('metadata') is not None:
posted_metadata = post_data['metadata'] posted_metadata = post_data['metadata']
# update existing metadata with submitted metadata (which can be partial) # update existing metadata with submitted metadata (which can be partial)
# IMPORTANT NOTE: if the client passed pack 'null' (None) for a piece of metadata that means 'remove it' # IMPORTANT NOTE: if the client passed pack 'null' (None) for a piece of metadata that means 'remove it'
for metadata_key in posted_metadata.keys(): for metadata_key in posted_metadata.keys():
# let's strip out any metadata fields from the postback which have been identified as system metadata # let's strip out any metadata fields from the postback which have been identified as system metadata
# and therefore should not be user-editable, so we should accept them back from the client # and therefore should not be user-editable, so we should accept them back from the client
if metadata_key in module.system_metadata_fields: if metadata_key in module.system_metadata_fields:
del posted_metadata[metadata_key] del posted_metadata[metadata_key]
elif posted_metadata[metadata_key] is None: elif posted_metadata[metadata_key] is None:
# remove both from passed in collection as well as the collection read in from the modulestore # remove both from passed in collection as well as the collection read in from the modulestore
if metadata_key in module.metadata: if metadata_key in module.metadata:
del module.metadata[metadata_key] del module.metadata[metadata_key]
del posted_metadata[metadata_key] del posted_metadata[metadata_key]
# overlay the new metadata over the modulestore sourced collection to support partial updates # overlay the new metadata over the modulestore sourced collection to support partial updates
module.metadata.update(posted_metadata) module.metadata.update(posted_metadata)
# commit to datastore # commit to datastore
store.update_metadata(location, module.metadata) store.update_metadata(location, module.metadata)
import json import json
import shutil import shutil
from django.test.client import Client from django.test.client import Client
from override_settings import override_settings from django.test.utils import override_settings
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from path import path from path import path
...@@ -10,6 +10,7 @@ import json ...@@ -10,6 +10,7 @@ import json
from fs.osfs import OSFS from fs.osfs import OSFS
import copy import copy
from mock import Mock from mock import Mock
from json import dumps, loads
from student.models import Registration from student.models import Registration
from django.contrib.auth.models import User from django.contrib.auth.models import User
...@@ -26,10 +27,12 @@ from xmodule.contentstore.django import contentstore ...@@ -26,10 +27,12 @@ from xmodule.contentstore.django import contentstore
from xmodule.templates import update_templates from xmodule.templates import update_templates
from xmodule.modulestore.xml_exporter import export_to_xml from xmodule.modulestore.xml_exporter import export_to_xml
from xmodule.modulestore.xml_importer import import_from_xml from xmodule.modulestore.xml_importer import import_from_xml
from xmodule.templates import update_templates
from xmodule.capa_module import CapaDescriptor from xmodule.capa_module import CapaDescriptor
from xmodule.course_module import CourseDescriptor from xmodule.course_module import CourseDescriptor
from xmodule.seq_module import SequenceDescriptor from xmodule.seq_module import SequenceDescriptor
from xmodule.modulestore.exceptions import ItemNotFoundError
TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE) TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE)
TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data') TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data')
...@@ -207,6 +210,24 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -207,6 +210,24 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
# check for custom_tags # check for custom_tags
self.verify_content_existence(ms, root_dir, location, 'custom_tags', 'custom_tag_template') self.verify_content_existence(ms, root_dir, location, 'custom_tags', 'custom_tag_template')
# check for graiding_policy.json
fs = OSFS(root_dir / 'test_export/policies/6.002_Spring_2012')
self.assertTrue(fs.exists('grading_policy.json'))
course = ms.get_item(location)
# compare what's on disk compared to what we have in our course
with fs.open('grading_policy.json','r') as grading_policy:
on_disk = loads(grading_policy.read())
self.assertEqual(on_disk, course.definition['data']['grading_policy'])
#check for policy.json
self.assertTrue(fs.exists('policy.json'))
# compare what's on disk to what we have in the course module
with fs.open('policy.json','r') as course_policy:
on_disk = loads(course_policy.read())
self.assertIn('course/6.002_Spring_2012', on_disk)
self.assertEqual(on_disk['course/6.002_Spring_2012'], course.metadata)
# remove old course # remove old course
delete_course(ms, cs, location) delete_course(ms, cs, location)
...@@ -357,7 +378,7 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -357,7 +378,7 @@ class ContentStoreTest(ModuleStoreTestCase):
resp = self.client.get(reverse('course_index', kwargs=data)) resp = self.client.get(reverse('course_index', kwargs=data))
self.assertContains(resp, self.assertContains(resp,
'<a href="/MITx/999/course/Robot_Super_Course" class="class-name">Robot Super Course</a>', '<article class="courseware-overview" data-course-id="i4x://MITx/999/course/Robot_Super_Course">',
status_code=200, status_code=200,
html=True) html=True)
...@@ -380,11 +401,11 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -380,11 +401,11 @@ class ContentStoreTest(ModuleStoreTestCase):
def test_capa_module(self): def test_capa_module(self):
"""Test that a problem treats markdown specially.""" """Test that a problem treats markdown specially."""
CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course') course = CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
problem_data = { problem_data = {
'parent_location': 'i4x://MITx/999/course/Robot_Super_Course', 'parent_location': 'i4x://MITx/999/course/Robot_Super_Course',
'template': 'i4x://edx/templates/problem/Empty' 'template': 'i4x://edx/templates/problem/Blank_Common_Problem'
} }
resp = self.client.post(reverse('clone_item'), problem_data) resp = self.client.post(reverse('clone_item'), problem_data)
...@@ -399,3 +420,32 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -399,3 +420,32 @@ class ContentStoreTest(ModuleStoreTestCase):
self.assertIn('markdown', context, "markdown is missing from context") self.assertIn('markdown', context, "markdown is missing from context")
self.assertIn('markdown', problem.metadata, "markdown is missing from metadata") self.assertIn('markdown', problem.metadata, "markdown is missing from metadata")
self.assertNotIn('markdown', problem.editable_metadata_fields, "Markdown slipped into the editable metadata fields") self.assertNotIn('markdown', problem.editable_metadata_fields, "Markdown slipped into the editable metadata fields")
class TemplateTestCase(ModuleStoreTestCase):
def test_template_cleanup(self):
ms = modulestore('direct')
# insert a bogus template in the store
bogus_template_location = Location('i4x', 'edx', 'templates', 'html', 'bogus')
source_template_location = Location('i4x', 'edx', 'templates', 'html', 'Blank_HTML_Page')
ms.clone_item(source_template_location, bogus_template_location)
verify_create = ms.get_item(bogus_template_location)
self.assertIsNotNone(verify_create)
# now run cleanup
update_templates()
# now try to find dangling template, it should not be in DB any longer
asserted = False
try:
verify_create = ms.get_item(bogus_template_location)
except ItemNotFoundError:
asserted = True
self.assertTrue(asserted)
...@@ -143,10 +143,6 @@ class CourseDetailsViewTest(CourseTestCase): ...@@ -143,10 +143,6 @@ class CourseDetailsViewTest(CourseTestCase):
def test_update_and_fetch(self): def test_update_and_fetch(self):
details = CourseDetails.fetch(self.course_location) details = CourseDetails.fetch(self.course_location)
resp = self.client.get(reverse('course_settings', kwargs={'org': self.course_location.org, 'course': self.course_location.course,
'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'})
...@@ -249,7 +245,7 @@ class CourseGradingTest(CourseTestCase): ...@@ -249,7 +245,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")
......
import json import json
import shutil import shutil
from django.test.client import Client from django.test.client import Client
from override_settings import override_settings
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from path import path from path import path
...@@ -86,7 +85,6 @@ class ContentStoreTestCase(ModuleStoreTestCase): ...@@ -86,7 +85,6 @@ class ContentStoreTestCase(ModuleStoreTestCase):
# Now make sure that the user is now actually activated # Now make sure that the user is now actually activated
self.assertTrue(user(email).is_active) self.assertTrue(user(email).is_active)
class AuthTestCase(ContentStoreTestCase): class AuthTestCase(ContentStoreTestCase):
"""Check that various permissions-related things work""" """Check that various permissions-related things work"""
......
...@@ -2,7 +2,6 @@ import json ...@@ -2,7 +2,6 @@ import json
import copy import copy
from time import time from time import time
from django.test import TestCase from django.test import TestCase
from override_settings import override_settings
from django.conf import settings from django.conf import settings
from student.models import Registration from student.models import Registration
......
...@@ -59,6 +59,7 @@ from cms.djangoapps.models.settings.course_details import CourseDetails,\ ...@@ -59,6 +59,7 @@ from cms.djangoapps.models.settings.course_details import CourseDetails,\
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
from lxml import etree from lxml import etree
from django.shortcuts import redirect
# to install PIL on MacOSX: 'easy_install http://dist.repoze.org/PIL-1.1.6.tar.gz' # to install PIL on MacOSX: 'easy_install http://dist.repoze.org/PIL-1.1.6.tar.gz'
...@@ -81,6 +82,11 @@ def signup(request): ...@@ -81,6 +82,11 @@ def signup(request):
csrf_token = csrf(request)['csrf_token'] csrf_token = csrf(request)['csrf_token']
return render_to_response('signup.html', {'csrf': csrf_token}) return render_to_response('signup.html', {'csrf': csrf_token})
def old_login_redirect(request):
'''
Redirect to the active login url.
'''
return redirect('login', permanent=True)
@ssl_login_shortcut @ssl_login_shortcut
@ensure_csrf_cookie @ensure_csrf_cookie
...@@ -94,6 +100,11 @@ def login_page(request): ...@@ -94,6 +100,11 @@ def login_page(request):
'forgot_password_link': "//{base}/#forgot-password-modal".format(base=settings.LMS_BASE), 'forgot_password_link': "//{base}/#forgot-password-modal".format(base=settings.LMS_BASE),
}) })
def howitworks(request):
if request.user.is_authenticated():
return index(request)
else:
return render_to_response('howitworks.html', {})
# ==== Views for any logged-in user ================================== # ==== Views for any logged-in user ==================================
...@@ -122,7 +133,8 @@ def index(request): ...@@ -122,7 +133,8 @@ def index(request):
course.location.course, course.location.course,
course.location.name])) course.location.name]))
for course in courses], for course in courses],
'user': request.user 'user': request.user,
'disable_course_creation': settings.MITX_FEATURES.get('DISABLE_COURSE_CREATION', False) and not request.user.is_staff
}) })
...@@ -272,7 +284,7 @@ def edit_unit(request, location): ...@@ -272,7 +284,7 @@ def edit_unit(request, location):
template.display_name, template.display_name,
template.location.url(), template.location.url(),
'markdown' in template.metadata, 'markdown' in template.metadata,
template.location.name == 'Empty' 'empty' in template.metadata
)) ))
components = [ components = [
...@@ -729,8 +741,6 @@ def clone_item(request): ...@@ -729,8 +741,6 @@ def clone_item(request):
#@login_required #@login_required
#@ensure_csrf_cookie #@ensure_csrf_cookie
def upload_asset(request, org, course, coursename): def upload_asset(request, org, course, coursename):
''' '''
cdodge: this method allows for POST uploading of files into the course asset library, which will cdodge: this method allows for POST uploading of files into the course asset library, which will
...@@ -795,8 +805,6 @@ def upload_asset(request, org, course, coursename): ...@@ -795,8 +805,6 @@ def upload_asset(request, org, course, coursename):
''' '''
This view will return all CMS users who are editors for the specified course This view will return all CMS users who are editors for the specified course
''' '''
@login_required @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
def manage_users(request, location): def manage_users(request, location):
...@@ -818,7 +826,7 @@ def manage_users(request, location): ...@@ -818,7 +826,7 @@ def manage_users(request, location):
}) })
def create_json_response(errmsg=None): def create_json_response(errmsg = None):
if errmsg is not None: if errmsg is not None:
resp = HttpResponse(json.dumps({'Status': 'Failed', 'ErrMsg': errmsg})) resp = HttpResponse(json.dumps({'Status': 'Failed', 'ErrMsg': errmsg}))
else: else:
...@@ -830,8 +838,6 @@ def create_json_response(errmsg=None): ...@@ -830,8 +838,6 @@ def create_json_response(errmsg=None):
This POST-back view will add a user - specified by email - to the list of editors for This POST-back view will add a user - specified by email - to the list of editors for
the specified course the specified course
''' '''
@expect_json @expect_json
@login_required @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
...@@ -864,8 +870,6 @@ def add_user(request, location): ...@@ -864,8 +870,6 @@ def add_user(request, location):
This POST-back view will remove a user - specified by email - from the list of editors for This POST-back view will remove a user - specified by email - from the list of editors for
the specified course the specified course
''' '''
@expect_json @expect_json
@login_required @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
...@@ -1123,8 +1127,31 @@ def get_course_settings(request, org, course, name): ...@@ -1123,8 +1127,31 @@ def get_course_settings(request, org, course, name):
course_details = CourseDetails.fetch(location) course_details = CourseDetails.fetch(location)
return render_to_response('settings.html', { return render_to_response('settings.html', {
'active_tab': 'settings',
'context_course': course_module, 'context_course': course_module,
'course_location' : location,
'course_details' : json.dumps(course_details, cls=CourseSettingsEncoder)
})
@login_required
@ensure_csrf_cookie
def course_config_graders_page(request, org, course, name):
"""
Send models and views as well as html for editing the course settings to the client.
org, course, name: Attributes of the Location for the item to edit
"""
location = ['i4x', org, course, 'course', name]
# check that logged in user has permissions to this item
if not has_access(request.user, location):
raise PermissionDenied()
course_module = modulestore().get_item(location)
course_details = CourseGradingModel.fetch(location)
return render_to_response('settings_graders.html', {
'context_course': course_module,
'course_location' : location,
'course_details': json.dumps(course_details, cls=CourseSettingsEncoder) 'course_details': json.dumps(course_details, cls=CourseSettingsEncoder)
}) })
...@@ -1259,6 +1286,10 @@ def edge(request): ...@@ -1259,6 +1286,10 @@ def edge(request):
@login_required @login_required
@expect_json @expect_json
def create_new_course(request): def create_new_course(request):
if settings.MITX_FEATURES.get('DISABLE_COURSE_CREATION', False) and not request.user.is_staff:
raise PermissionDenied()
# This logic is repeated in xmodule/modulestore/tests/factories.py # This logic is repeated in xmodule/modulestore/tests/factories.py
# so if you change anything here, you need to also change it there. # so if you change anything here, you need to also change it there.
# TODO: write a test that creates two courses, one with the factory and # TODO: write a test that creates two courses, one with the factory and
......
...@@ -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
...@@ -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
......
...@@ -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
......
<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"> </div>
<input type="text" class="long"
id="course-grading-assignment-name" value="<%= model.get('type') %>"> <div class="field text" id="field-course-grading-assignment-shortname">
<span class="tip tip-stacked">e.g. Homework, Labs, Midterm Exams, Final Exam</span> <label for="course-grading-assignment-shortname">Abbreviation:</label>
</div> <input type="text" class="short" id="course-grading-assignment-shortname" value="<%= model.get('short_label') %>" />
</div> <span class="tip tip-inline">e.g. HW, Midterm</span>
</div> </div>
<div class="row row-col2"> <div class="field text" id="field-course-grading-assignment-gradeweight">
<label for="course-grading-shortname">Abbreviation:</label> <label for="course-grading-assignment-gradeweight">Weight of Total Grade</label>
<input type="text" class="short" id="course-grading-assignment-gradeweight" value = "<%= model.get('weight') %>" />
<div class="field"> <span class="tip tip-inline">e.g. 25%</span>
<div class="input course-grading-shortname"> </div>
<input type="text" class="short"
id="course-grading-assignment-shortname" <div class="field text" id="field-course-grading-assignment-totalassignments">
value="<%= model.get('short_label') %>"> <label for="course-grading-assignment-totalassignments">Total
<span class="tip tip-inline">e.g. HW, Midterm, Final</span> Number</label>
</div> <input type="text" class="short" id="course-grading-assignment-totalassignments" value = "<%= model.get('min_count') %>" />
</div> <span class="tip tip-inline">total exercises assigned</span>
</div> </div>
<div class="row row-col2"> <div class="field text" id="field-course-grading-assignment-droppable">
<label for="course-grading-gradeweight">Weight of Total <label for="course-grading-assignment-droppable">Number of
Grade:</label> Droppable</label>
<input type="text" class="short" id="course-grading-assignment-droppable" value = "<%= model.get('drop_count') %>" />
<div class="field"> <span class="tip tip-inline">total exercises that won't be graded</span>
<div class="input course-grading-gradeweight"> </div>
<input type="text" class="short"
id="course-grading-assignment-gradeweight" <div class="actions">
value = "<%= model.get('weight') %>"> <a href="#" class="button delete-button standard remove-item remove-grading-data"><span class="delete-icon"></span>Delete</a>
<span class="tip tip-inline">e.g. 25%</span> </div>
</div>
</div>
</div>
<div class="row row-col2">
<label for="course-grading-assignment-totalassignments">Total
Number:</label>
<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>
</div>
</div>
</div>
<div class="row row-col2">
<label for="course-grading-assignment-droppable">Number of
Droppable:</label>
<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>
</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
if (!CMS.Models['Settings']) CMS.Models.Settings = new Object(); if (!CMS.Models['Settings']) CMS.Models.Settings = new Object();
CMS.Models.Settings.CourseDetails = Backbone.Model.extend({ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({
defaults: { defaults: {
location : null, // the course's Location model, required location : null, // the course's Location model, required
start_date: null, // maps to 'start' start_date: null, // maps to 'start'
end_date: null, // maps to 'end' end_date: null, // maps to 'end'
enrollment_start: null, enrollment_start: null,
enrollment_end: null, enrollment_end: null,
syllabus: null, syllabus: null,
overview: "", overview: "",
intro_video: null, intro_video: null,
effort: null // an int or null effort: null // an int or null
}, },
// When init'g from html script, ensure you pass {parse: true} as an option (2nd arg to reset) // When init'g from html script, ensure you pass {parse: true} as an option (2nd arg to reset)
parse: function(attributes) { parse: function(attributes) {
if (attributes['course_location']) { if (attributes['course_location']) {
attributes.location = new CMS.Models.Location(attributes.course_location, {parse:true}); attributes.location = new CMS.Models.Location(attributes.course_location, {parse:true});
} }
if (attributes['start_date']) { if (attributes['start_date']) {
attributes.start_date = new Date(attributes.start_date); attributes.start_date = new Date(attributes.start_date);
} }
if (attributes['end_date']) { if (attributes['end_date']) {
attributes.end_date = new Date(attributes.end_date); attributes.end_date = new Date(attributes.end_date);
} }
if (attributes['enrollment_start']) { if (attributes['enrollment_start']) {
attributes.enrollment_start = new Date(attributes.enrollment_start); attributes.enrollment_start = new Date(attributes.enrollment_start);
} }
if (attributes['enrollment_end']) { if (attributes['enrollment_end']) {
attributes.enrollment_end = new Date(attributes.enrollment_end); attributes.enrollment_end = new Date(attributes.enrollment_end);
} }
return attributes; return attributes;
}, },
validate: function(newattrs) { validate: function(newattrs) {
// Returns either nothing (no return call) so that validate works or an object of {field: errorstring} pairs // Returns either nothing (no return call) so that validate works or an object of {field: errorstring} pairs
// A bit funny in that the video key validation is asynchronous; so, it won't stop the validation. // A bit funny in that the video key validation is asynchronous; so, it won't stop the validation.
var errors = {}; var errors = {};
if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) { if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) {
errors.end_date = "The course end date cannot be before the course start date."; errors.end_date = "The course end date cannot be before the course start date.";
} }
if (newattrs.start_date && newattrs.enrollment_start && newattrs.start_date < newattrs.enrollment_start) { if (newattrs.start_date && newattrs.enrollment_start && newattrs.start_date < newattrs.enrollment_start) {
errors.enrollment_start = "The course start date cannot be before the enrollment start date."; errors.enrollment_start = "The course start date cannot be before the enrollment start date.";
} }
if (newattrs.enrollment_start && newattrs.enrollment_end && newattrs.enrollment_start >= newattrs.enrollment_end) { if (newattrs.enrollment_start && newattrs.enrollment_end && newattrs.enrollment_start >= newattrs.enrollment_end) {
errors.enrollment_end = "The enrollment start date cannot be after the enrollment end date."; errors.enrollment_end = "The enrollment start date cannot be after the enrollment end date.";
} }
if (newattrs.end_date && newattrs.enrollment_end && newattrs.end_date < newattrs.enrollment_end) { if (newattrs.end_date && newattrs.enrollment_end && newattrs.end_date < newattrs.enrollment_end) {
errors.enrollment_end = "The enrollment end date cannot be after the course end date."; errors.enrollment_end = "The enrollment end date cannot be after the course end date.";
} }
if (newattrs.intro_video && newattrs.intro_video !== this.get('intro_video')) { if (newattrs.intro_video && newattrs.intro_video !== this.get('intro_video')) {
if (this._videokey_illegal_chars.exec(newattrs.intro_video)) { if (this._videokey_illegal_chars.exec(newattrs.intro_video)) {
errors.intro_video = "Key should only contain letters, numbers, _, or -"; errors.intro_video = "Key should only contain letters, numbers, _, or -";
} }
// TODO check if key points to a real video using google's youtube api // TODO check if key points to a real video using google's youtube api
} }
if (!_.isEmpty(errors)) return errors; if (!_.isEmpty(errors)) return errors;
// NOTE don't return empty errors as that will be interpreted as an error state // NOTE don't return empty errors as that will be interpreted as an error state
}, },
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.set('intro_video', newsource);
if (this.get('intro_video') !== newsource) this.save('intro_video', newsource, }
{ error : CMS.ServerError});
} return this.videosourceSample();
},
return this.videosourceSample(); videosourceSample : function() {
}, if (this.has('intro_video')) return "http://www.youtube.com/embed/" + this.get('intro_video');
videosourceSample : function() { else return "";
if (this.has('intro_video')) return "http://www.youtube.com/embed/" + this.get('intro_video'); }
else return "";
}
}); });
if (!CMS.Models['Settings']) CMS.Models.Settings = new Object(); if (!CMS.Models['Settings']) CMS.Models.Settings = new Object();
CMS.Models.Settings.CourseSettings = Backbone.Model.extend({ 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, problems: null,
problems: null, discussions: null
discussions: null },
},
retrieve: function(submodel, callback) { retrieve: function(submodel, callback) {
if (this.get(submodel)) callback(); if (this.get(submodel)) callback();
else { else {
var cachethis = this; var cachethis = this;
switch (submodel) { switch (submodel) {
case 'details': case 'details':
var details = new CMS.Models.Settings.CourseDetails({location: this.get('courseLocation')}); var details = new CMS.Models.Settings.CourseDetails({location: this.get('courseLocation')});
details.fetch( { details.fetch( {
success : function(model) { success : function(model) {
cachethis.set('details', model); cachethis.set('details', model);
callback(model); callback(model);
} }
}); });
break; break;
case 'grading': case 'grading':
var grading = new CMS.Models.Settings.CourseGradingPolicy({course_location: this.get('courseLocation')}); var grading = new CMS.Models.Settings.CourseGradingPolicy({course_location: this.get('courseLocation')});
grading.fetch( { grading.fetch( {
success : function(model) { success : function(model) {
cachethis.set('grading', model); cachethis.set('grading', model);
callback(model); callback(model);
} }
}); });
break; break;
default: default:
break; break;
} }
} }
} }
}) })
\ No newline at end of file
...@@ -58,6 +58,9 @@ $(document).ready(function() { ...@@ -58,6 +58,9 @@ $(document).ready(function() {
drop: onSectionReordered, drop: onSectionReordered,
greedy: true greedy: true
}); });
// stop clicks on drag bars from doing their thing w/o stopping drag
$('.drag-handle').click(function(e) {e.preventDefault(); });
}); });
...@@ -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;
......
...@@ -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;
} }
...@@ -285,4 +285,11 @@ ...@@ -285,4 +285,11 @@
padding: 0; padding: 0;
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
...@@ -54,4 +54,16 @@ ...@@ -54,4 +54,16 @@
@include white-button; @include white-button;
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
...@@ -54,4 +54,118 @@ del { ...@@ -54,4 +54,118 @@ del {
table { 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 {
background: #fff;
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 { a {
border-radius: 0 0 3px 3px; border-top: 0px;
border-bottom: 1px solid $darkGreen;
} }
} }
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;
...@@ -34,4 +94,4 @@ $brightGreen: rgb(22, 202, 87); ...@@ -34,4 +94,4 @@ $brightGreen: rgb(22, 202, 87);
$disabledGreen: rgb(124, 206, 153); $disabledGreen: rgb(124, 206, 153);
$darkGreen: rgb(52, 133, 76); $darkGreen: rgb(52, 133, 76);
$lightBluishGrey: rgb(197, 207, 223); $lightBluishGrey: rgb(197, 207, 223);
$lightBluishGrey2: rgb(213, 220, 228); $lightBluishGrey2: rgb(213, 220, 228);
\ No newline at end of file
@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:
<a href="#" class="new-button new-course-button"><span class="plus-icon white"></span> New Course</a> % if not disable_course_creation:
<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>
<!-- 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>
</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> </div>
</form> </aside>
</article> </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 class="title title-1">Sign Up for edX Studio</h1>
<h1>Register 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"> </div>
<label>Password</label>
<input name="password" type="password"> <fieldset>
</div> <legend class="sr">Required Information to Sign Up for edX Studio</legend>
<div class="row">
<label>Public Username</label> <ol class="list-input">
<input name="username" type="text"> <li class="field text required" id="field-email">
</div> <label for="email">Email Address</label>
<div class="row"> <input id="email" type="email" name="email" placeholder="e.g. jane.doe@gmail.com" />
<label>Full Name</label> </li>
<input name="name" type="text">
</div> <li class="field text required" id="field-password">
<div class="row"> <label for="password">Password</label>
<div class="split"> <input id="password" type="password" name="password" />
<label>Your Location</label> </li>
<input name="location" type="text">
<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 class="field text" id="field-language">
<label for="language">Preferred Language</label>
<input class="short" id="language" type="text" name="language" />
</div>
</li>
<li class="field checkbox required" id="field-tos">
<input id="tos" name="terms_of_service" type="checkbox" value="true" />
<label for="tos">I agree to the Terms of Service</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>
<!-- 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>
</article>
<aside class="content-supplementary" role="complimentary">
<h2 class="sr">Common Studio Questions</h2>
<div class="bit">
<h3 class="title-3">Who is Studio for?</h3>
<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>
<div class="split">
<label>Preferred Language</label> <div class="bit">
<input name="language" type="text"> <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> </div>
</div> </aside>
<div class="row"> </section>
<label class="terms-of-service"> </div>
<input name="terms_of_service" type="checkbox" value="true"> </%block>
I agree to the
<a href="#">Terms of Service</a> <%block name="jsextra">
</label> <script type="text/javascript">
</div> (function() {
<!-- no honor code for CMS, but need it because we're using the lms student object --> $("form :input").focus(function() {
<input name="honor_code" type="checkbox" value="true" checked="true" hidden="true"> $("label[for='" + this.id + "']").addClass("is-focused");
}).blur(function() {
<div class="row form-actions submit"> $("label").removeClass("is-focused");
<input name="submit" type="submit" value="Create My Account" class="create-account-button">
<p class="enrolled">Already enrolled? <a href="/">Log In.</a></p>
</div>
</form>
</article>
<script type="text/javascript">
(function() {
function getCookie(name) {
return $.cookie(name);
}
function postJSON(url, data, callback) {
$.ajax({type:'POST',
url: url,
dataType: 'json',
data: data,
success: callback,
headers : {'X-CSRFToken':getCookie('csrftoken')}
}); });
}
$('form#register_form').submit(function(e) { function getCookie(name) {
e.preventDefault(); return $.cookie(name);
var submit_data = $('#register_form').serialize(); }
postJSON('/create_account', // form validation
submit_data, function postJSON(url, data, callback) {
function(json) { $.ajax({type:'POST',
if(json.success) { url: url,
location.href = "${reverse('index')}"; dataType: 'json',
} else { data: data,
$('#register_error').html(json.value).stop().slideDown(150); success: callback,
headers : {'X-CSRFToken':getCookie('csrftoken')}
});
}
$('form#register_form').submit(function(e) {
e.preventDefault();
var submit_data = $('#register_form').serialize();
postJSON('/create_account',
submit_data,
function(json) {
if(json.success) {
location.href = "${reverse('index')}";
} else {
$('#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() {
...@@ -13,12 +14,20 @@ ...@@ -13,12 +14,20 @@
state: '${unit_state}' state: '${unit_state}'
}) })
}); });
$(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="new-component-template"> <ul class="problem-type-tabs nav-tabs">
% for name, location, has_markdown, is_empty in templates: <li class="current">
<a class="link-tab" href="#tab1">Common Problem Types</a>
% if is_empty: </li>
<li class="editor-md empty"> <li>
<a href="#" data-location="${location}"> <a class="link-tab" href="#tab2">Advanced</a>
<span class="name"><i class="ss-icon ss-symbolicons-block">&#xE714;</i> ${name}</span> </li>
<span class="editor-indicator">Simple <span class="sr">Editor</span></span> </ul>
</a> % endif
</li> <div class="tab current" id="tab1">
<ul class="new-component-template">
% elif has_markdown: % for name, location, has_markdown, is_empty in templates:
<li class="editor-md"> % if has_markdown or type != "problem":
<a href="#" data-location="${location}"> % if is_empty:
<span class="name"><i class="ss-icon ss-symbolicons-block">&#xE714;</i> ${name}</span> <li class="editor-md empty">
<span class="editor-indicator">Simple <span class="sr">Editor</span></span> <a href="#" data-location="${location}">
</a> <span class="name"> ${name}</span>
</li> </a>
</li>
% else:
<li class="editor-manual"> % else:
<a href="#" data-location="${location}"> <li class="editor-md">
<span class="name"><i class="ss-icon ss-symbolicons-block">&#x1F527;</i> ${name}</span> <a href="#" data-location="${location}">
<span class="editor-indicator">Advanced <span class="sr">Editor</span></span> <span class="name"> ${name}</span>
</a> </a>
</li> </li>
% endif % endif
% endif
%endfor
</ul> %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>
</li>
% else:
<li class="editor-manual">
<a href="#" data-location="${location}">
<span class="name"> ${name}</span>
</a>
</li>
% endif
% endif
% endfor
</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="left">
% if context_course:
<% ctx_loc = context_course.location %>
<a href="/" class="home"><span class="small-home-icon"></span></a>
<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>
% endif
</div>
<div class="right"> <div class="wrapper wrapper-left ">
<span class="username">${ user.username }</span> <h1 class="branding"><a href="/">edX Studio</a></h1>
% if user.is_authenticated():
<a href="${reverse('logout')}" class="log-out"><span class="log-out-icon"></span></a> % if context_course:
% else: <% ctx_loc = context_course.location %>
<a href="${reverse('login')}">Log in</a> <div class="info-course">
% endif <h2 class="sr">Current Course:</h2>
<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>
<nav class="nav-course primary nav-dropdown" role="navigation">
<h2 class="sr">PH207x's Navigation:</h2>
<ol>
<li class="nav-item nav-course-courseware">
<h3 class="title"><span class="label-prefix">Course </span>Content <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-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>
</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>
</li>
<li class="nav-item nav-course-tools">
<h3 class="title">Tools <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-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 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>
</ul>
</div>
</div>
</li>
</ol>
</nav>
% 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>
% 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> </div>
</div> </header>
<nav class="class-nav-bar"> </div>
% if context_course: \ No newline at end of file
<% ctx_loc = context_course.location %>
<ul class="class-nav inner-wrapper">
<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>
<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>
<li><a href="${reverse('manage_users', kwargs=dict(location=ctx_loc))}" id='users-tab'>Users</a></li>
<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><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><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>
% endif
</nav>
</header>
<%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,9 +43,10 @@ urlpatterns = ('', ...@@ -42,9 +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>[^/]+)/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'),
...@@ -76,13 +78,15 @@ urlpatterns = ('', ...@@ -76,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'),
......
...@@ -2,7 +2,7 @@ import django.test ...@@ -2,7 +2,7 @@ import django.test
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.conf import settings from django.conf import settings
from override_settings import override_settings from django.test.utils import override_settings
from course_groups.models import CourseUserGroup from course_groups.models import CourseUserGroup
from course_groups.cohorts import (get_cohort, get_course_cohorts, from course_groups.cohorts import (get_cohort, get_course_cohorts,
......
...@@ -13,12 +13,18 @@ log = logging.getLogger(__name__) ...@@ -13,12 +13,18 @@ log = logging.getLogger(__name__)
def _url_replace_regex(prefix): def _url_replace_regex(prefix):
"""
Match static urls in quotes that don't end in '?raw'.
To anyone contemplating making this more complicated:
http://xkcd.com/1171/
"""
return r""" return r"""
(?x) # flags=re.VERBOSE (?x) # flags=re.VERBOSE
(?P<quote>\\?['"]) # the opening quotes (?P<quote>\\?['"]) # the opening quotes
(?P<prefix>{prefix}) # theeprefix (?P<prefix>{prefix}) # the prefix
(?P<rest>.*?) # everything else in the url (?P<rest>.*?) # everything else in the url
(?P=quote) # the first matching closing quote (?P=quote) # the first matching closing quote
""".format(prefix=prefix) """.format(prefix=prefix)
...@@ -74,12 +80,20 @@ def replace_static_urls(text, data_directory, course_namespace=None): ...@@ -74,12 +80,20 @@ def replace_static_urls(text, data_directory, course_namespace=None):
quote = match.group('quote') quote = match.group('quote')
rest = match.group('rest') rest = match.group('rest')
# Don't mess with things that end in '?raw'
if rest.endswith('?raw'):
return original
# course_namespace is not None, then use studio style urls # course_namespace is not None, then use studio style urls
if course_namespace is not None and not isinstance(modulestore(), XMLModuleStore): if course_namespace is not None and not isinstance(modulestore(), XMLModuleStore):
url = StaticContent.convert_legacy_static_url(rest, course_namespace) url = StaticContent.convert_legacy_static_url(rest, course_namespace)
# In debug mode, if we can find the url as is,
elif settings.DEBUG and finders.find(rest, True):
return original
# Otherwise, look the file up in staticfiles_storage, and append the data directory if needed # Otherwise, look the file up in staticfiles_storage, and append the data directory if needed
else: else:
course_path = "/".join((data_directory, rest)) course_path = "/".join((data_directory, rest))
try: try:
if staticfiles_storage.exists(rest): if staticfiles_storage.exists(rest):
url = staticfiles_storage.url(rest) url = staticfiles_storage.url(rest)
......
from nose.tools import assert_equals import re
from static_replace import replace_static_urls, replace_course_urls
from nose.tools import assert_equals, assert_true, assert_false
from static_replace import (replace_static_urls, replace_course_urls,
_url_replace_regex)
from mock import patch, Mock from mock import patch, Mock
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.mongo import MongoModuleStore from xmodule.modulestore.mongo import MongoModuleStore
...@@ -75,3 +78,34 @@ def test_data_dir_fallback(mock_storage, mock_modulestore, mock_settings): ...@@ -75,3 +78,34 @@ def test_data_dir_fallback(mock_storage, mock_modulestore, mock_settings):
mock_storage.exists.return_value = False mock_storage.exists.return_value = False
assert_equals('"/static/data_dir/file.png"', replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY)) assert_equals('"/static/data_dir/file.png"', replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY))
def test_raw_static_check():
"""
Make sure replace_static_urls leaves alone things that end in '.raw'
"""
path = '"/static/foo.png?raw"'
assert_equals(path, replace_static_urls(path, DATA_DIRECTORY))
text = 'text <tag a="/static/js/capa/protex/protex.nocache.js?raw"/><div class="'
assert_equals(path, replace_static_urls(path, text))
def test_regex():
yes = ('"/static/foo.png"',
'"/static/foo.png"',
"'/static/foo.png'")
no = ('"/not-static/foo.png"',
'"/static/foo', # no matching quote
)
regex = _url_replace_regex('/static/')
for s in yes:
print 'Should match: {0!r}'.format(s)
assert_true(re.match(regex, s))
for s in no:
print 'Should not match: {0!r}'.format(s)
assert_false(re.match(regex, s))
from django.conf import settings from django.conf import settings
from django.test import TestCase from django.test import TestCase
import os import os
from override_settings import override_settings from django.test.utils import override_settings
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from status import get_site_status_msg from status import get_site_status_msg
......
...@@ -18,10 +18,13 @@ def jsdate_to_time(field): ...@@ -18,10 +18,13 @@ def jsdate_to_time(field):
""" """
if field is None: if field is None:
return field return field
elif isinstance(field, basestring): # iso format but ignores time zone assuming it's Z elif isinstance(field, basestring):
d = datetime.datetime(*map(int, re.split('[^\d]', field)[:6])) # stop after seconds. Debatable # ISO format but ignores time zone assuming it's Z.
d = datetime.datetime(*map(int, re.split('[^\d]', field)[:6])) # stop after seconds. Debatable
return d.utctimetuple() return d.utctimetuple()
elif isinstance(field, int) or isinstance(field, float): elif isinstance(field, (int, long, float)):
return time.gmtime(field / 1000) return time.gmtime(field / 1000)
elif isinstance(field, time.struct_time): elif isinstance(field, time.struct_time):
return field return field
else:
raise ValueError("Couldn't convert %r to time" % field)
...@@ -632,8 +632,14 @@ class MultipleChoiceResponse(LoncapaResponse): ...@@ -632,8 +632,14 @@ class MultipleChoiceResponse(LoncapaResponse):
# define correct choices (after calling secondary setup) # define correct choices (after calling secondary setup)
xml = self.xml xml = self.xml
cxml = xml.xpath('//*[@id=$id]//choice[@correct="true"]', id=xml.get('id')) cxml = xml.xpath('//*[@id=$id]//choice', id=xml.get('id'))
self.correct_choices = [contextualize_text(choice.get('name'), self.context) for choice in cxml]
# contextualize correct attribute and then select ones for which
# correct = "true"
self.correct_choices = [
contextualize_text(choice.get('name'), self.context)
for choice in cxml
if contextualize_text(choice.get('correct'), self.context) == "true"]
def mc_setup_response(self): def mc_setup_response(self):
''' '''
...@@ -999,7 +1005,7 @@ def sympy_check2(): ...@@ -999,7 +1005,7 @@ def sympy_check2():
self.context['debug'] = self.system.DEBUG self.context['debug'] = self.system.DEBUG
# exec the check function # exec the check function
if type(self.code) == str: if isinstance(self.code, basestring):
try: try:
exec self.code in self.context['global_context'], self.context exec self.code in self.context['global_context'], self.context
correct = self.context['correct'] correct = self.context['correct']
......
...@@ -50,6 +50,7 @@ ...@@ -50,6 +50,7 @@
}, },
smartIndent: false smartIndent: false
}); });
$("#textbox_${id}").find('.CodeMirror-scroll').height(${int(13.5*eval(rows))});
}); });
</script> </script>
</section> </section>
<section id="designprotein2dinput_${id}" class="designprotein2dinput"> <section id="designprotein2dinput_${id}" class="designprotein2dinput">
<div class="script_placeholder" data-src="/static/js/capa/protex/protex.nocache.js"/> <div class="script_placeholder" data-src="/static/js/capa/protex/protex.nocache.js?raw"/>
<div class="script_placeholder" data-src="${applet_loader}"/> <div class="script_placeholder" data-src="${applet_loader}"/>
% if status == 'unsubmitted': % if status == 'unsubmitted':
......
test_problem_display.js
test_problem_generator.js
test_problem_grader.js
xproblem.js
\ No newline at end of file
// Generated by CoffeeScript 1.4.0
(function() {
var MinimaxProblemDisplay, root,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
MinimaxProblemDisplay = (function(_super) {
__extends(MinimaxProblemDisplay, _super);
function MinimaxProblemDisplay(state, submission, evaluation, container, submissionField, parameters) {
this.state = state;
this.submission = submission;
this.evaluation = evaluation;
this.container = container;
this.submissionField = submissionField;
this.parameters = parameters != null ? parameters : {};
MinimaxProblemDisplay.__super__.constructor.call(this, this.state, this.submission, this.evaluation, this.container, this.submissionField, this.parameters);
}
MinimaxProblemDisplay.prototype.render = function() {};
MinimaxProblemDisplay.prototype.createSubmission = function() {
var id, value, _ref, _results;
this.newSubmission = {};
if (this.submission != null) {
_ref = this.submission;
_results = [];
for (id in _ref) {
value = _ref[id];
_results.push(this.newSubmission[id] = value);
}
return _results;
}
};
MinimaxProblemDisplay.prototype.getCurrentSubmission = function() {
return this.newSubmission;
};
return MinimaxProblemDisplay;
})(XProblemDisplay);
root = typeof exports !== "undefined" && exports !== null ? exports : this;
root.TestProblemDisplay = TestProblemDisplay;
}).call(this);
// Generated by CoffeeScript 1.4.0
(function() {
var TestProblemGenerator, root,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
TestProblemGenerator = (function(_super) {
__extends(TestProblemGenerator, _super);
function TestProblemGenerator(seed, parameters) {
this.parameters = parameters != null ? parameters : {};
TestProblemGenerator.__super__.constructor.call(this, seed, this.parameters);
}
TestProblemGenerator.prototype.generate = function() {
this.problemState.value = this.parameters.value;
return this.problemState;
};
return TestProblemGenerator;
})(XProblemGenerator);
root = typeof exports !== "undefined" && exports !== null ? exports : this;
root.generatorClass = TestProblemGenerator;
}).call(this);
// Generated by CoffeeScript 1.4.0
(function() {
var TestProblemGrader, root,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
TestProblemGrader = (function(_super) {
__extends(TestProblemGrader, _super);
function TestProblemGrader(submission, problemState, parameters) {
this.submission = submission;
this.problemState = problemState;
this.parameters = parameters != null ? parameters : {};
TestProblemGrader.__super__.constructor.call(this, this.submission, this.problemState, this.parameters);
}
TestProblemGrader.prototype.solve = function() {
return this.solution = {
0: this.problemState.value
};
};
TestProblemGrader.prototype.grade = function() {
var allCorrect, id, value, valueCorrect, _ref;
if (!(this.solution != null)) {
this.solve();
}
allCorrect = true;
_ref = this.solution;
for (id in _ref) {
value = _ref[id];
valueCorrect = this.submission != null ? value === this.submission[id] : false;
this.evaluation[id] = valueCorrect;
if (!valueCorrect) {
allCorrect = false;
}
}
return allCorrect;
};
return TestProblemGrader;
})(XProblemGrader);
root = typeof exports !== "undefined" && exports !== null ? exports : this;
root.graderClass = TestProblemGrader;
}).call(this);
// Generated by CoffeeScript 1.4.0
(function() {
var XProblemDisplay, XProblemGenerator, XProblemGrader, root;
XProblemGenerator = (function() {
function XProblemGenerator(seed, parameters) {
this.parameters = parameters != null ? parameters : {};
this.random = new MersenneTwister(seed);
this.problemState = {};
}
XProblemGenerator.prototype.generate = function() {
return console.error("Abstract method called: XProblemGenerator.generate");
};
return XProblemGenerator;
})();
XProblemDisplay = (function() {
function XProblemDisplay(state, submission, evaluation, container, submissionField, parameters) {
this.state = state;
this.submission = submission;
this.evaluation = evaluation;
this.container = container;
this.submissionField = submissionField;
this.parameters = parameters != null ? parameters : {};
}
XProblemDisplay.prototype.render = function() {
return console.error("Abstract method called: XProblemDisplay.render");
};
XProblemDisplay.prototype.updateSubmission = function() {
return this.submissionField.val(JSON.stringify(this.getCurrentSubmission()));
};
XProblemDisplay.prototype.getCurrentSubmission = function() {
return console.error("Abstract method called: XProblemDisplay.getCurrentSubmission");
};
return XProblemDisplay;
})();
XProblemGrader = (function() {
function XProblemGrader(submission, problemState, parameters) {
this.submission = submission;
this.problemState = problemState;
this.parameters = parameters != null ? parameters : {};
this.solution = null;
this.evaluation = {};
}
XProblemGrader.prototype.solve = function() {
return console.error("Abstract method called: XProblemGrader.solve");
};
XProblemGrader.prototype.grade = function() {
return console.error("Abstract method called: XProblemGrader.grade");
};
return XProblemGrader;
})();
root = typeof exports !== "undefined" && exports !== null ? exports : this;
root.XProblemGenerator = XProblemGenerator;
root.XProblemDisplay = XProblemDisplay;
root.XProblemGrader = XProblemGrader;
}).call(this);
...@@ -53,7 +53,7 @@ def get_logger_config(log_dir, ...@@ -53,7 +53,7 @@ def get_logger_config(log_dir,
logging_env=logging_env, logging_env=logging_env,
hostname=hostname) hostname=hostname)
handlers = ['console', 'local', 'null'] if debug else ['console', handlers = ['console', 'local'] if debug else ['console',
'syslogger-remote', 'local'] 'syslogger-remote', 'local']
logger_config = { logger_config = {
...@@ -84,12 +84,6 @@ def get_logger_config(log_dir, ...@@ -84,12 +84,6 @@ def get_logger_config(log_dir,
'level': 'ERROR', 'level': 'ERROR',
'class': 'newrelic_logging.NewRelicHandler', 'class': 'newrelic_logging.NewRelicHandler',
'formatter': 'raw', 'formatter': 'raw',
},
'null' : {
'level': 'CRITICAL',
'class': 'logging.handlers.SysLogHandler',
'address': syslog_addr,
'formatter': 'syslog_format',
} }
}, },
'loggers': { 'loggers': {
...@@ -98,26 +92,11 @@ def get_logger_config(log_dir, ...@@ -98,26 +92,11 @@ def get_logger_config(log_dir,
'level': 'DEBUG', 'level': 'DEBUG',
'propagate': False, 'propagate': False,
}, },
'django.db.backends': {
'handlers': ['null'],
'propagate': False,
'level':'DEBUG',
},
'django_comment_client.utils' : {
'handlers': ['null'],
'propagate': False,
'level':'DEBUG',
},
'pipeline.compilers' : {
'handlers': ['null'],
'propagate': False,
'level':'DEBUG',
},
'': { '': {
'handlers': handlers, 'handlers': handlers,
'level': 'DEBUG', 'level': 'DEBUG',
'propagate': False 'propagate': False
} },
} }
} }
......
...@@ -34,8 +34,10 @@ setup( ...@@ -34,8 +34,10 @@ setup(
"section = xmodule.backcompat_module:SemanticSectionDescriptor", "section = xmodule.backcompat_module:SemanticSectionDescriptor",
"sequential = xmodule.seq_module:SequenceDescriptor", "sequential = xmodule.seq_module:SequenceDescriptor",
"slides = xmodule.backcompat_module:TranslateCustomTagDescriptor", "slides = xmodule.backcompat_module:TranslateCustomTagDescriptor",
"timelimit = xmodule.timelimit_module:TimeLimitDescriptor",
"vertical = xmodule.vertical_module:VerticalDescriptor", "vertical = xmodule.vertical_module:VerticalDescriptor",
"video = xmodule.video_module:VideoDescriptor", "video = xmodule.video_module:VideoDescriptor",
"videoalpha = xmodule.videoalpha_module:VideoAlphaDescriptor",
"videodev = xmodule.backcompat_module:TranslateCustomTagDescriptor", "videodev = xmodule.backcompat_module:TranslateCustomTagDescriptor",
"videosequence = xmodule.seq_module:SequenceDescriptor", "videosequence = xmodule.seq_module:SequenceDescriptor",
"discussion = xmodule.discussion_module:DiscussionDescriptor", "discussion = xmodule.discussion_module:DiscussionDescriptor",
...@@ -43,7 +45,8 @@ setup( ...@@ -43,7 +45,8 @@ setup(
"static_tab = xmodule.html_module:StaticTabDescriptor", "static_tab = xmodule.html_module:StaticTabDescriptor",
"custom_tag_template = xmodule.raw_module:RawDescriptor", "custom_tag_template = xmodule.raw_module:RawDescriptor",
"about = xmodule.html_module:AboutDescriptor", "about = xmodule.html_module:AboutDescriptor",
"graphical_slider_tool = xmodule.gst_module:GraphicalSliderToolDescriptor" "graphical_slider_tool = xmodule.gst_module:GraphicalSliderToolDescriptor",
] "foldit = xmodule.foldit_module:FolditDescriptor",
]
} }
) )
...@@ -703,15 +703,15 @@ class CapaDescriptor(RawDescriptor): ...@@ -703,15 +703,15 @@ class CapaDescriptor(RawDescriptor):
def get_context(self): def get_context(self):
_context = RawDescriptor.get_context(self) _context = RawDescriptor.get_context(self)
_context.update({'markdown': self.metadata.get('markdown', '')}) _context.update({'markdown': self.metadata.get('markdown', ''),
'enable_markdown' : 'markdown' in self.metadata})
return _context return _context
@property @property
def editable_metadata_fields(self): def editable_metadata_fields(self):
"""Remove metadata from the editable fields since it has its own editor""" """Remove any metadata from the editable fields which have their own editor or shouldn't be edited by user."""
subset = super(CapaDescriptor, self).editable_metadata_fields subset = [field for field in super(CapaDescriptor,self).editable_metadata_fields
if 'markdown' in subset: if field not in ['markdown', 'empty']]
subset.remove('markdown')
return subset return subset
......
...@@ -446,7 +446,7 @@ class CourseDescriptor(SequenceDescriptor): ...@@ -446,7 +446,7 @@ class CourseDescriptor(SequenceDescriptor):
# utility function to get datetime objects for dates used to # utility function to get datetime objects for dates used to
# compute the is_new flag and the sorting_score # compute the is_new flag and the sorting_score
def to_datetime(timestamp): def to_datetime(timestamp):
return datetime.fromtimestamp(time.mktime(timestamp)) return datetime(*timestamp[:6])
def get_date(field): def get_date(field):
timetuple = self._try_parse_time(field) timetuple = self._try_parse_time(field)
...@@ -648,7 +648,7 @@ class CourseDescriptor(SequenceDescriptor): ...@@ -648,7 +648,7 @@ class CourseDescriptor(SequenceDescriptor):
raise ValueError("First appointment date must be before last appointment date") raise ValueError("First appointment date must be before last appointment date")
if self.registration_end_date > self.last_eligible_appointment_date: if self.registration_end_date > self.last_eligible_appointment_date:
raise ValueError("Registration end date must be before last appointment date") raise ValueError("Registration end date must be before last appointment date")
self.exam_url = exam_info.get('Exam_URL')
def _try_parse_time(self, key): def _try_parse_time(self, key):
""" """
...@@ -704,6 +704,10 @@ class CourseDescriptor(SequenceDescriptor): ...@@ -704,6 +704,10 @@ class CourseDescriptor(SequenceDescriptor):
else: else:
return None return None
def get_test_center_exam(self, exam_series_code):
exams = [exam for exam in self.test_center_exams if exam.exam_series_code == exam_series_code]
return exams[0] if len(exams) == 1 else None
@property @property
def title(self): def title(self):
return self.display_name return self.display_name
......
...@@ -120,7 +120,7 @@ div.combined-rubric-container { ...@@ -120,7 +120,7 @@ div.combined-rubric-container {
} }
} }
b.rubric-category { span.rubric-category {
font-size: .9em; font-size: .9em;
} }
padding-bottom: 5px; padding-bottom: 5px;
......
import logging
from lxml import etree
from dateutil import parser
from pkg_resources import resource_string
from xmodule.editing_module import EditingDescriptor
from xmodule.x_module import XModule
from xmodule.xml_module import XmlDescriptor
log = logging.getLogger(__name__)
class FolditModule(XModule):
def __init__(self, system, location, definition, descriptor,
instance_state=None, shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition, descriptor,
instance_state, shared_state, **kwargs)
# ooh look--I'm lazy, so hardcoding the 7.00x required level.
# If we need it generalized, can pull from the xml later
self.required_level = 4
self.required_sublevel = 5
def parse_due_date():
"""
Pull out the date, or None
"""
s = self.metadata.get("due")
if s:
return parser.parse(s)
else:
return None
self.due_str = self.metadata.get("due", "None")
self.due = parse_due_date()
def is_complete(self):
"""
Did the user get to the required level before the due date?
"""
# We normally don't want django dependencies in xmodule. foldit is
# special. Import this late to avoid errors with things not yet being
# initialized.
from foldit.models import PuzzleComplete
complete = PuzzleComplete.is_level_complete(
self.system.anonymous_student_id,
self.required_level,
self.required_sublevel,
self.due)
return complete
def completed_puzzles(self):
"""
Return a list of puzzles that this user has completed, as an array of
dicts:
[ {'set': int,
'subset': int,
'created': datetime} ]
The list is sorted by set, then subset
"""
from foldit.models import PuzzleComplete
return sorted(
PuzzleComplete.completed_puzzles(self.system.anonymous_student_id),
key=lambda d: (d['set'], d['subset']))
def get_html(self):
"""
Render the html for the module.
"""
goal_level = '{0}-{1}'.format(
self.required_level,
self.required_sublevel)
context = {
'due': self.due_str,
'success': self.is_complete(),
'goal_level': goal_level,
'completed': self.completed_puzzles(),
}
return self.system.render_template('foldit.html', context)
def get_score(self):
"""
0 / 1 based on whether student has gotten far enough.
"""
score = 1 if self.is_complete() else 0
return {'score': score,
'total': self.max_score()}
def max_score(self):
return 1
class FolditDescriptor(XmlDescriptor, EditingDescriptor):
"""
Module for adding open ended response questions to courses
"""
mako_template = "widgets/html-edit.html"
module_class = FolditModule
filename_extension = "xml"
stores_state = True
has_score = True
template_dir_name = "foldit"
js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]}
js_module_name = "HTMLEditingDescriptor"
# The grade changes without any student interaction with the edx website,
# so always need to actually check.
always_recalculate_grades = True
@classmethod
def definition_from_xml(cls, xml_object, system):
"""
For now, don't need anything from the xml
"""
return {}
...@@ -3,7 +3,11 @@ from xmodule.raw_module import RawDescriptor ...@@ -3,7 +3,11 @@ from xmodule.raw_module import RawDescriptor
class HiddenModule(XModule): class HiddenModule(XModule):
pass def get_html(self):
if self.system.user_is_staff:
return "ERROR: This module is unknown--students will not see it at all"
else:
return ""
class HiddenDescriptor(RawDescriptor): class HiddenDescriptor(RawDescriptor):
......
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