Merge remote-tracking branch 'origin/master' into ps-cms-backbone
Conflicts: cms/djangoapps/contentstore/views.py cms/static/coffee/unit.coffee cms/templates/index.html cms/templates/widgets/sequence-edit.html
15
Gemfile.lock
@@ -1,15 +0,0 @@
|
||||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
bourbon (1.3.6)
|
||||
sass (>= 3.1)
|
||||
rake (0.9.2.2)
|
||||
sass (3.1.15)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
bourbon (~> 1.3.6)
|
||||
rake
|
||||
sass (= 3.1.15)
|
||||
@@ -0,0 +1,29 @@
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.xml import XMLModuleStore
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def import_from_xml(org, course, data_dir):
|
||||
"""
|
||||
Import the specified xml data_dir into the django defined modulestore,
|
||||
using org and course as the location org and course.
|
||||
"""
|
||||
module_store = XMLModuleStore(org, course, data_dir, 'xmodule.raw_module.RawDescriptor', eager=True)
|
||||
for module in module_store.modules.itervalues():
|
||||
|
||||
# TODO (cpennington): This forces import to overrite the same items.
|
||||
# This should in the future create new revisions of the items on import
|
||||
try:
|
||||
modulestore().create_item(module.location)
|
||||
except:
|
||||
log.exception('Item already exists at %s' % module.location.url())
|
||||
pass
|
||||
if 'data' in module.definition:
|
||||
modulestore().update_item(module.location, module.definition['data'])
|
||||
if 'children' in module.definition:
|
||||
modulestore().update_children(module.location, module.definition['children'])
|
||||
modulestore().update_metadata(module.location, dict(module.metadata))
|
||||
|
||||
return module_store.course
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
###
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.xml import XMLModuleStore
|
||||
from contentstore import import_from_xml
|
||||
|
||||
unnamed_modules = 0
|
||||
|
||||
@@ -18,12 +17,4 @@ class Command(BaseCommand):
|
||||
raise CommandError("import requires 3 arguments: <org> <course> <data directory>")
|
||||
|
||||
org, course, data_dir = args
|
||||
|
||||
module_store = XMLModuleStore(org, course, data_dir, 'xmodule.raw_module.RawDescriptor', eager=True)
|
||||
for module in module_store.modules.itervalues():
|
||||
modulestore().create_item(module.location)
|
||||
if 'data' in module.definition:
|
||||
modulestore().update_item(module.location, module.definition['data'])
|
||||
if 'children' in module.definition:
|
||||
modulestore().update_children(module.location, module.definition['children'])
|
||||
modulestore().update_metadata(module.location, dict(module.metadata))
|
||||
import_from_xml(org, course, data_dir)
|
||||
|
||||
@@ -1,21 +1,36 @@
|
||||
from mitxmako.shortcuts import render_to_response
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from django_future.csrf import ensure_csrf_cookie
|
||||
from django.http import HttpResponse
|
||||
from util.json_request import expect_json
|
||||
import json
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django_future.csrf import ensure_csrf_cookie
|
||||
from fs.osfs import OSFS
|
||||
from django.core.urlresolvers import reverse
|
||||
from xmodule.modulestore import Location
|
||||
from github_sync import repo_path_from_location, export_to_github
|
||||
|
||||
from mitxmako.shortcuts import render_to_response
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def index(request):
|
||||
courses = modulestore().get_items(['i4x', None, None, 'course', None])
|
||||
return render_to_response('index.html', {
|
||||
'courses': [(course.metadata['display_name'],
|
||||
reverse('course_index', args=[
|
||||
course.location.org,
|
||||
course.location.course,
|
||||
course.location.name]))
|
||||
for course in courses]
|
||||
})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def course_index(request, org, course, name):
|
||||
# TODO (cpennington): These need to be read in from the active user
|
||||
org = 'mit.edu'
|
||||
course = '6002xs12'
|
||||
name = '6.002_Spring_2012'
|
||||
course = modulestore().get_item(['i4x', org, course, 'course', name])
|
||||
weeks = course.get_children()
|
||||
return render_to_response('index.html', {'weeks': weeks})
|
||||
return render_to_response('course_index.html', {'weeks': weeks})
|
||||
|
||||
|
||||
def edit_item(request):
|
||||
@@ -34,6 +49,14 @@ def save_item(request):
|
||||
item_id = request.POST['id']
|
||||
data = json.loads(request.POST['data'])
|
||||
modulestore().update_item(item_id, data)
|
||||
|
||||
# Export the course back to github
|
||||
course_location = Location(item_id)._replace(category='course', name=None)
|
||||
courses = modulestore().get_items(course_location)
|
||||
for course in courses:
|
||||
repo_path = repo_path_from_location(course.location)
|
||||
export_to_github(course, repo_path, "CMS Edit")
|
||||
|
||||
return HttpResponse(json.dumps({}))
|
||||
|
||||
|
||||
|
||||
75
cms/djangoapps/github_sync/__init__.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from fs.osfs import OSFS
|
||||
from git import Repo, PushInfo
|
||||
|
||||
from contentstore import import_from_xml
|
||||
from xmodule.modulestore import Location
|
||||
|
||||
from .exceptions import GithubSyncError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
def import_from_github(repo_settings):
|
||||
"""
|
||||
Imports data into the modulestore based on the XML stored on github
|
||||
|
||||
repo_settings is a dictionary with the following keys:
|
||||
path: file system path to the local git repo
|
||||
branch: name of the branch to track on github
|
||||
org: name of the organization to use in the imported course
|
||||
course: name of the coures to use in the imported course
|
||||
"""
|
||||
repo_path = repo_settings['path']
|
||||
|
||||
if not os.path.isdir(repo_path):
|
||||
Repo.clone_from(repo_settings['origin'], repo_path)
|
||||
|
||||
git_repo = Repo(repo_path)
|
||||
origin = git_repo.remotes.origin
|
||||
origin.fetch()
|
||||
|
||||
# Do a hard reset to the remote branch so that we have a clean import
|
||||
git_repo.git.checkout(repo_settings['branch'])
|
||||
git_repo.head.reset('origin/%s' % repo_settings['branch'], index=True, working_tree=True)
|
||||
|
||||
return git_repo.head.commit.hexsha, import_from_xml(repo_settings['org'], repo_settings['course'], repo_path)
|
||||
|
||||
|
||||
def repo_path_from_location(location):
|
||||
location = Location(location)
|
||||
for name, repo in settings.REPOS.items():
|
||||
if repo['org'] == location.org and repo['course'] == location.course:
|
||||
return repo['path']
|
||||
|
||||
|
||||
def export_to_github(course, repo_path, commit_message):
|
||||
fs = OSFS(repo_path)
|
||||
xml = course.export_to_xml(fs)
|
||||
|
||||
with fs.open('course.xml', 'w') as course_xml:
|
||||
course_xml.write(xml)
|
||||
|
||||
git_repo = Repo(repo_path)
|
||||
if git_repo.is_dirty():
|
||||
git_repo.git.add(A=True)
|
||||
git_repo.git.commit(m=commit_message)
|
||||
|
||||
origin = git_repo.remotes.origin
|
||||
if settings.MITX_FEATURES['GITHUB_PUSH']:
|
||||
push_infos = origin.push()
|
||||
if len(push_infos) > 1:
|
||||
log.error('Unexpectedly pushed multiple heads: {infos}'.format(
|
||||
infos="\n".join(str(info.summary) for info in push_infos)
|
||||
))
|
||||
|
||||
if push_infos[0].flags & PushInfo.ERROR:
|
||||
log.error('Failed push: flags={p.flags}, local_ref={p.local_ref}, '
|
||||
'remote_ref_string={p.remote_ref_string}, '
|
||||
'remote_ref={p.remote_ref}, old_commit={p.old_commit}, '
|
||||
'summary={p.summary})'.format(p=push_infos[0]))
|
||||
raise GithubSyncError('Failed to push: {info}'.format(
|
||||
info=str(push_infos[0].summary)
|
||||
))
|
||||
2
cms/djangoapps/github_sync/exceptions.py
Normal file
@@ -0,0 +1,2 @@
|
||||
class GithubSyncError(Exception):
|
||||
pass
|
||||
96
cms/djangoapps/github_sync/tests/__init__.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from django.test import TestCase
|
||||
from path import path
|
||||
import shutil
|
||||
from github_sync import import_from_github, export_to_github, repo_path_from_location
|
||||
from git import Repo
|
||||
from django.conf import settings
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore import Location
|
||||
from override_settings import override_settings
|
||||
from github_sync.exceptions import GithubSyncError
|
||||
|
||||
|
||||
class GithubSyncTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.working_dir = path(settings.TEST_ROOT)
|
||||
self.repo_dir = self.working_dir / 'local_repo'
|
||||
self.remote_dir = self.working_dir / 'remote_repo'
|
||||
shutil.copytree('common/test/data/toy', self.remote_dir)
|
||||
|
||||
remote = Repo.init(self.remote_dir)
|
||||
remote.git.add(A=True)
|
||||
remote.git.commit(m='Initial commit')
|
||||
remote.git.config("receive.denyCurrentBranch", "ignore")
|
||||
|
||||
modulestore().collection.drop()
|
||||
|
||||
self.import_revision, self.import_course = import_from_github({
|
||||
'path': self.repo_dir,
|
||||
'origin': self.remote_dir,
|
||||
'branch': 'master',
|
||||
'org': 'org',
|
||||
'course': 'course'
|
||||
})
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.repo_dir)
|
||||
shutil.rmtree(self.remote_dir)
|
||||
|
||||
def test_initialize_repo(self):
|
||||
"""
|
||||
Test that importing from github will create a repo if the repo doesn't already exist
|
||||
"""
|
||||
self.assertEquals(1, len(Repo(self.repo_dir).head.reference.log()))
|
||||
|
||||
def test_import_contents(self):
|
||||
"""
|
||||
Test that the import loads the correct course into the modulestore
|
||||
"""
|
||||
self.assertEquals('Toy Course', self.import_course.metadata['display_name'])
|
||||
self.assertIn(
|
||||
Location('i4x://org/course/chapter/Overview'),
|
||||
[child.location for child in self.import_course.get_children()])
|
||||
self.assertEquals(1, len(self.import_course.get_children()))
|
||||
|
||||
@override_settings(MITX_FEATURES={'GITHUB_PUSH': False})
|
||||
def test_export_no_pash(self):
|
||||
"""
|
||||
Test that with the GITHUB_PUSH feature disabled, no content is pushed to the remote
|
||||
"""
|
||||
export_to_github(self.import_course, self.repo_dir, 'Test no-push')
|
||||
self.assertEquals(1, Repo(self.remote_dir).head.commit.count())
|
||||
|
||||
@override_settings(MITX_FEATURES={'GITHUB_PUSH': True})
|
||||
def test_export_push(self):
|
||||
"""
|
||||
Test that with GITHUB_PUSH enabled, content is pushed to the remote
|
||||
"""
|
||||
self.import_course.metadata['display_name'] = 'Changed display name'
|
||||
export_to_github(self.import_course, self.repo_dir, 'Test push')
|
||||
self.assertEquals(2, Repo(self.remote_dir).head.commit.count())
|
||||
|
||||
@override_settings(MITX_FEATURES={'GITHUB_PUSH': True})
|
||||
def test_export_conflict(self):
|
||||
"""
|
||||
Test that if there is a conflict when pushing to the remote repo, nothing is pushed and an exception is raised
|
||||
"""
|
||||
self.import_course.metadata['display_name'] = 'Changed display name'
|
||||
|
||||
remote = Repo(self.remote_dir)
|
||||
remote.git.commit(allow_empty=True, m="Testing conflict commit")
|
||||
|
||||
self.assertRaises(GithubSyncError, export_to_github, self.import_course, self.repo_dir, 'Test push')
|
||||
self.assertEquals(2, remote.head.reference.commit.count())
|
||||
self.assertEquals("Testing conflict commit\n", remote.head.reference.commit.message)
|
||||
|
||||
|
||||
@override_settings(REPOS={'namea': {'path': 'patha', 'org': 'orga', 'course': 'coursea'},
|
||||
'nameb': {'path': 'pathb', 'org': 'orgb', 'course': 'courseb'}})
|
||||
class RepoPathLookupTestCase(TestCase):
|
||||
def test_successful_lookup(self):
|
||||
self.assertEquals('patha', repo_path_from_location('i4x://orga/coursea/course/foo'))
|
||||
self.assertEquals('pathb', repo_path_from_location('i4x://orgb/courseb/course/foo'))
|
||||
|
||||
def test_failed_lookup(self):
|
||||
self.assertEquals(None, repo_path_from_location('i4x://c/c/course/foo'))
|
||||
53
cms/djangoapps/github_sync/tests/test_views.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import json
|
||||
from django.test.client import Client
|
||||
from django.test import TestCase
|
||||
from mock import patch, Mock
|
||||
from override_settings import override_settings
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
@override_settings(REPOS={'repo': {'path': 'path', 'branch': 'branch'}})
|
||||
class PostReceiveTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
|
||||
@patch('github_sync.views.export_to_github')
|
||||
@patch('github_sync.views.import_from_github')
|
||||
def test_non_branch(self, import_from_github, export_to_github):
|
||||
self.client.post('/github_service_hook', {'payload': json.dumps({
|
||||
'ref': 'refs/tags/foo'})
|
||||
})
|
||||
self.assertFalse(import_from_github.called)
|
||||
self.assertFalse(export_to_github.called)
|
||||
|
||||
@patch('github_sync.views.export_to_github')
|
||||
@patch('github_sync.views.import_from_github')
|
||||
def test_non_watched_repo(self, import_from_github, export_to_github):
|
||||
self.client.post('/github_service_hook', {'payload': json.dumps({
|
||||
'ref': 'refs/heads/branch',
|
||||
'repository': {'name': 'bad_repo'}})
|
||||
})
|
||||
self.assertFalse(import_from_github.called)
|
||||
self.assertFalse(export_to_github.called)
|
||||
|
||||
@patch('github_sync.views.export_to_github')
|
||||
@patch('github_sync.views.import_from_github')
|
||||
def test_non_tracked_branch(self, import_from_github, export_to_github):
|
||||
self.client.post('/github_service_hook', {'payload': json.dumps({
|
||||
'ref': 'refs/heads/non_branch',
|
||||
'repository': {'name': 'repo'}})
|
||||
})
|
||||
self.assertFalse(import_from_github.called)
|
||||
self.assertFalse(export_to_github.called)
|
||||
|
||||
@patch('github_sync.views.export_to_github')
|
||||
@patch('github_sync.views.import_from_github', return_value=(Mock(), Mock()))
|
||||
def test_tracked_branch(self, import_from_github, export_to_github):
|
||||
self.client.post('/github_service_hook', {'payload': json.dumps({
|
||||
'ref': 'refs/heads/branch',
|
||||
'repository': {'name': 'repo'}})
|
||||
})
|
||||
import_from_github.assert_called_with(settings.REPOS['repo'])
|
||||
mock_revision, mock_course = import_from_github.return_value
|
||||
export_to_github.assert_called_with(mock_course, 'path', "Changes from cms import of revision %s" % mock_revision)
|
||||
|
||||
52
cms/djangoapps/github_sync/views.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import logging
|
||||
import json
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.conf import settings
|
||||
from django_future.csrf import csrf_exempt
|
||||
|
||||
from . import import_from_github, export_to_github
|
||||
|
||||
log = logging.getLogger()
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def github_post_receive(request):
|
||||
"""
|
||||
This view recieves post-receive requests from github whenever one of
|
||||
the watched repositiories changes.
|
||||
|
||||
It is responsible for updating the relevant local git repo,
|
||||
importing the new version of the course (if anything changed),
|
||||
and then pushing back to github any changes that happened as part of the
|
||||
import.
|
||||
|
||||
The github request format is described here: https://help.github.com/articles/post-receive-hooks
|
||||
"""
|
||||
|
||||
payload = json.loads(request.POST['payload'])
|
||||
|
||||
ref = payload['ref']
|
||||
|
||||
if not ref.startswith('refs/heads/'):
|
||||
log.info('Ignore changes to non-branch ref %s' % ref)
|
||||
return HttpResponse('Ignoring non-branch')
|
||||
|
||||
branch_name = ref.replace('refs/heads/', '', 1)
|
||||
|
||||
repo_name = payload['repository']['name']
|
||||
|
||||
if repo_name not in settings.REPOS:
|
||||
log.info('No repository matching %s found' % repo_name)
|
||||
return HttpResponse('No Repo Found')
|
||||
|
||||
repo = settings.REPOS[repo_name]
|
||||
|
||||
if repo['branch'] != branch_name:
|
||||
log.info('Ignoring changes to non-tracked branch %s in repo %s' % (branch_name, repo_name))
|
||||
return HttpResponse('Ignoring non-tracked branch')
|
||||
|
||||
revision, course = import_from_github(repo)
|
||||
export_to_github(course, repo['path'], "Changes from cms import of revision %s" % revision)
|
||||
|
||||
return HttpResponse('Push recieved')
|
||||
@@ -31,6 +31,7 @@ from path import path
|
||||
|
||||
MITX_FEATURES = {
|
||||
'USE_DJANGO_PIPELINE': True,
|
||||
'GITHUB_PUSH': False,
|
||||
}
|
||||
|
||||
############################# SET PATH INFORMATION #############################
|
||||
|
||||
@@ -29,8 +29,48 @@ DATABASES = {
|
||||
}
|
||||
}
|
||||
|
||||
REPO_ROOT = ENV_ROOT / "content"
|
||||
|
||||
REPOS = {
|
||||
'edx4edx': {
|
||||
'path': REPO_ROOT / "edx4edx",
|
||||
'org': 'edx',
|
||||
'course': 'edx4edx',
|
||||
'branch': 'for_cms',
|
||||
'origin': 'git@github.com:MITx/edx4edx.git',
|
||||
},
|
||||
'6002x-fall-2012': {
|
||||
'path': REPO_ROOT / '6002x-fall-2012',
|
||||
'org': 'mit.edu',
|
||||
'course': '6.002x',
|
||||
'branch': 'for_cms',
|
||||
'origin': 'git@github.com:MITx/6002x-fall-2012.git',
|
||||
},
|
||||
'6.00x': {
|
||||
'path': REPO_ROOT / '6.00x',
|
||||
'org': 'mit.edu',
|
||||
'course': '6.00x',
|
||||
'branch': 'for_cms',
|
||||
'origin': 'git@github.com:MITx/6.00x.git',
|
||||
},
|
||||
'7.00x': {
|
||||
'path': REPO_ROOT / '7.00x',
|
||||
'org': 'mit.edu',
|
||||
'course': '7.00x',
|
||||
'branch': 'for_cms',
|
||||
'origin': 'git@github.com:MITx/7.00x.git',
|
||||
},
|
||||
'3.091x': {
|
||||
'path': REPO_ROOT / '3.091x',
|
||||
'org': 'mit.edu',
|
||||
'course': '3.091x',
|
||||
'branch': 'for_cms',
|
||||
'origin': 'git@github.com:MITx/3.091x.git',
|
||||
},
|
||||
}
|
||||
|
||||
CACHES = {
|
||||
# This is the cache used for most things. Askbot will not work without a
|
||||
# This is the cache used for most things. Askbot will not work without a
|
||||
# functioning cache -- it relies on caching to load its settings in places.
|
||||
# In staging/prod envs, the sessions also live here.
|
||||
'default': {
|
||||
|
||||
@@ -17,10 +17,18 @@ for app in os.listdir(PROJECT_ROOT / 'djangoapps'):
|
||||
NOSE_ARGS += ['--cover-package', app]
|
||||
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
|
||||
|
||||
TEST_ROOT = 'test_root'
|
||||
|
||||
MODULESTORE = {
|
||||
'host': 'localhost',
|
||||
'db': 'mongo_base',
|
||||
'collection': 'key_store',
|
||||
'default': {
|
||||
'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore',
|
||||
'OPTIONS': {
|
||||
'default_class': 'xmodule.raw_module.RawDescriptor',
|
||||
'host': 'localhost',
|
||||
'db': 'test_xmodule',
|
||||
'collection': 'modulestore',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DATABASES = {
|
||||
|
||||
15
cms/static/coffee/unit.coffee
Normal file
@@ -0,0 +1,15 @@
|
||||
class @Unit
|
||||
constructor: (@element_id, @module_id) ->
|
||||
@module = new window[$("##{@element_id}").attr('class')] 'module-html'
|
||||
|
||||
$("##{@element_id} .save-update").click (event) =>
|
||||
event.preventDefault()
|
||||
$.post("/save_item", {
|
||||
id: @module_id
|
||||
data: JSON.stringify(@module.save())
|
||||
})
|
||||
|
||||
$("##{@element_id} .cancel").click (event) =>
|
||||
event.preventDefault()
|
||||
CMS.edit_item(@module_id)
|
||||
|
||||
BIN
cms/static/img/content-types/chapter.png
Normal file
|
After Width: | Height: | Size: 135 B |
BIN
cms/static/img/content-types/html.png
Normal file
|
After Width: | Height: | Size: 352 B |
BIN
cms/static/img/content-types/lab.png
Normal file
|
After Width: | Height: | Size: 207 B |
BIN
cms/static/img/content-types/problem.png
Normal file
|
After Width: | Height: | Size: 235 B |
BIN
cms/static/img/content-types/problemset.png
Normal file
|
After Width: | Height: | Size: 222 B |
BIN
cms/static/img/content-types/sequential.png
Normal file
|
After Width: | Height: | Size: 122 B |
BIN
cms/static/img/content-types/vertical.png
Normal file
|
After Width: | Height: | Size: 144 B |
BIN
cms/static/img/content-types/video.png
Normal file
|
After Width: | Height: | Size: 129 B |
BIN
cms/static/img/content-types/videosequence.png
Normal file
|
After Width: | Height: | Size: 176 B |
@@ -32,11 +32,11 @@ input {
|
||||
|
||||
button, input[type="submit"], .button {
|
||||
background-color: $orange;
|
||||
color: #fff;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
padding: 8px 10px;
|
||||
border: 0;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
padding: 8px 10px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
||||
&:hover {
|
||||
background-color: shade($orange, 10%);
|
||||
@@ -45,9 +45,22 @@ button, input[type="submit"], .button {
|
||||
|
||||
#{$all-text-inputs}, textarea {
|
||||
border: 1px solid $dark-blue;
|
||||
font: $body-font-size $body-font-family;
|
||||
padding: 4px 6px;
|
||||
@include box-shadow(inset 0 3px 6px $light-blue);
|
||||
color: lighten($dark-blue, 30%);
|
||||
font: $body-font-size $body-font-family;
|
||||
outline: none;
|
||||
padding: 4px 6px;
|
||||
|
||||
&:hover {
|
||||
background: lighten($yellow, 13%);
|
||||
color: $dark-blue;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@include box-shadow(inset 0 3px 6px $light-blue, 0 0 3px lighten($bright-blue, 10%));
|
||||
color: $dark-blue;
|
||||
background: lighten($yellow, 13%);
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
@@ -56,7 +69,6 @@ textarea {
|
||||
line-height: lh();
|
||||
padding: 15px;
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
// Extends
|
||||
@@ -87,18 +99,22 @@ textarea {
|
||||
}
|
||||
|
||||
.draggable {
|
||||
width: 7px;
|
||||
min-height: 14px;
|
||||
background: url('../img/drag-handle.png') no-repeat center;
|
||||
text-indent: -9999px;
|
||||
display: block;
|
||||
float: right;
|
||||
cursor: move;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
@include position(absolute, 0px 0px 0 0);
|
||||
width: 30px;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.editable {
|
||||
&:hover {
|
||||
background: lighten($yellow, 10%);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 4px 10px;
|
||||
}
|
||||
@@ -107,13 +123,8 @@ textarea {
|
||||
.wip {
|
||||
outline: 1px solid #f00 !important;
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
content: "WIP";
|
||||
font-size: 8px;
|
||||
padding: 2px;
|
||||
background: #f00;
|
||||
color: #fff;
|
||||
@include position(absolute, 0px 0px 0 0);
|
||||
}
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
section.cal {
|
||||
@include box-sizing(border-box);
|
||||
padding: 25px;
|
||||
@include clearfix;
|
||||
padding: 25px;
|
||||
|
||||
> header {
|
||||
@include clearfix;
|
||||
@@ -18,25 +18,44 @@ section.cal {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
font-size: 14px;
|
||||
padding: 6px;
|
||||
padding: 6px 6px 6px 0;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
@include inline-block;
|
||||
float: right;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
&.actions {
|
||||
float: left;
|
||||
}
|
||||
|
||||
li {
|
||||
@include inline-block;
|
||||
margin-left: 6px;
|
||||
border-left: 1px solid #ddd;
|
||||
padding: 0 6px;
|
||||
margin-right: 6px;
|
||||
border-right: 1px solid #ddd;
|
||||
padding: 0 6px 0 0;
|
||||
|
||||
&:last-child {
|
||||
border-right: 0;
|
||||
margin-right: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
@include inline-block();
|
||||
font-size: 12px;
|
||||
@include inline-block;
|
||||
margin: 0 6px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
ul {
|
||||
@include inline-block();
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
@include inline-block();
|
||||
@@ -55,6 +74,8 @@ section.cal {
|
||||
border-top: 1px solid lighten($dark-blue, 40%);
|
||||
width: 100%;
|
||||
@include box-sizing(border-box);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
> li {
|
||||
border-right: 1px solid lighten($dark-blue, 40%);
|
||||
@@ -62,8 +83,13 @@ section.cal {
|
||||
@include box-sizing(border-box);
|
||||
float: left;
|
||||
width: flex-grid(3) + ((flex-gutter() * 3) / 4);
|
||||
background-color: lighten($light-blue, 2%);
|
||||
background-color: $light-blue;
|
||||
|
||||
&:hover {
|
||||
li.create-module {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
border-bottom: 1px solid lighten($dark-blue, 40%);
|
||||
@@ -77,103 +103,133 @@ section.cal {
|
||||
text-transform: uppercase;
|
||||
border-bottom: 1px solid lighten($dark-blue, 60%);
|
||||
padding: 6px;
|
||||
color: $bright-blue;
|
||||
margin: 0;
|
||||
|
||||
a {
|
||||
color: $bright-blue;
|
||||
display: block;
|
||||
padding: 6px;
|
||||
margin: -6px;
|
||||
|
||||
&:hover {
|
||||
color: darken($bright-blue, 10%);
|
||||
background: lighten($yellow, 10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
background: #fff;
|
||||
color: #888;
|
||||
border-bottom: 0;
|
||||
font-size: 12px;
|
||||
|
||||
&:hover {
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin-bottom: 1px;
|
||||
margin: 0 0 1px 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid darken($light-blue, 8%);
|
||||
padding: 6px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
background: lighten($yellow, 10%);
|
||||
background-color: lighten($yellow, 14%);
|
||||
|
||||
a.draggable {
|
||||
background-color: lighten($yellow, 14%);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.editable {
|
||||
padding: 3px 6px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: lighten($dark-blue, 10%);
|
||||
display: block;
|
||||
padding: 6px 35px 6px 6px;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($yellow, 10%);
|
||||
}
|
||||
|
||||
&.draggable {
|
||||
background-color: $light-blue;
|
||||
opacity: .3;
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($yellow, 10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.create-module {
|
||||
position: relative;
|
||||
opacity: 0;
|
||||
@include transition(all 3s ease-in-out);
|
||||
background: darken($light-blue, 2%);
|
||||
|
||||
> div {
|
||||
background: $dark-blue;
|
||||
@include box-shadow(0 0 5px darken($light-blue, 60%));
|
||||
@include box-sizing(border-box);
|
||||
display: none;
|
||||
margin-left: 3%;
|
||||
padding: 10px;
|
||||
@include position(absolute, 30px 0 0 0);
|
||||
width: 90%;
|
||||
background: rgba(#000, .9);
|
||||
padding: 10px;
|
||||
@include box-sizing(border-box);
|
||||
@include border-radius(3px);
|
||||
z-index: 99;
|
||||
|
||||
&:before {
|
||||
content: " ";
|
||||
display: block;
|
||||
background: rgba(#000, .8);
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
@include position(absolute, -5px 0 0 50%);
|
||||
@include transform(rotate(45deg));
|
||||
}
|
||||
|
||||
ul {
|
||||
li {
|
||||
border-bottom: 0;
|
||||
background: none;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
@include box-sizing(border-box);
|
||||
border-color: #000;
|
||||
padding: 6px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
@include box-sizing(border-box);
|
||||
width: 100%;
|
||||
|
||||
option {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $light-blue;
|
||||
float: right;
|
||||
|
||||
&:first-child {
|
||||
float: left;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
div {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,7 +237,7 @@ section.cal {
|
||||
}
|
||||
|
||||
section.new-section {
|
||||
margin-top: 10px;
|
||||
margin: 10px 0 40px;
|
||||
@include inline-block();
|
||||
position: relative;
|
||||
|
||||
@@ -247,30 +303,48 @@ section.cal {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.content
|
||||
section.cal {
|
||||
width: flex-grid(3) + flex-gutter();
|
||||
width: flex-grid(3);
|
||||
float: left;
|
||||
overflow: scroll;
|
||||
@include box-sizing(border-box);
|
||||
opacity: .4;
|
||||
@include transition();
|
||||
|
||||
> header ul {
|
||||
display: none;
|
||||
}
|
||||
background: darken($light-blue, 2%);
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
width: flex-grid(5) + flex-gutter();
|
||||
background-color: transparent;
|
||||
|
||||
+ section.main-content {
|
||||
width: flex-grid(7);
|
||||
opacity: .6;
|
||||
}
|
||||
}
|
||||
|
||||
> header {
|
||||
@include transition;
|
||||
overflow: hidden;
|
||||
|
||||
> a {
|
||||
display: none;
|
||||
}
|
||||
|
||||
ul {
|
||||
float: none;
|
||||
display: block;
|
||||
|
||||
li {
|
||||
ul {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
|
||||
55
cms/static/sass/_content-types.scss
Normal file
@@ -0,0 +1,55 @@
|
||||
.content-type {
|
||||
padding-left: 34px;
|
||||
background-position: 8px center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.videosequence a:first-child {
|
||||
@extend .content-type;
|
||||
background-image: url('/static/img/content-types/videosequence.png');
|
||||
}
|
||||
|
||||
.video a:first-child {
|
||||
@extend .content-type;
|
||||
background-image: url('/static/img/content-types/video.png');
|
||||
}
|
||||
|
||||
.problemset a:first-child {
|
||||
@extend .content-type;
|
||||
background-image: url('/static/img/content-types/problemset.png');
|
||||
}
|
||||
|
||||
.problem a:first-child {
|
||||
@extend .content-type;
|
||||
background-image: url('/static/img/content-types/problem.png');
|
||||
}
|
||||
|
||||
.lab a:first-child {
|
||||
@extend .content-type;
|
||||
background-image: url('/static/img/content-types/lab.png');
|
||||
}
|
||||
|
||||
.tab a:first-child {
|
||||
@extend .content-type;
|
||||
background-image: url('/static/img/content-types/lab.png');
|
||||
}
|
||||
|
||||
.html a:first-child {
|
||||
@extend .content-type;
|
||||
background-image: url('/static/img/content-types/html.png');
|
||||
}
|
||||
|
||||
.vertical a:first-child {
|
||||
@extend .content-type;
|
||||
background-image: url('/static/img/content-types/vertical.png');
|
||||
}
|
||||
|
||||
.sequential a:first-child {
|
||||
@extend .content-type;
|
||||
background-image: url('/static/img/content-types/sequential.png');
|
||||
}
|
||||
|
||||
.chapter a:first-child {
|
||||
@extend .content-type;
|
||||
background-image: url('/static/img/content-types/chapter.png');
|
||||
}
|
||||
@@ -5,6 +5,7 @@ body {
|
||||
|
||||
> section {
|
||||
display: table;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -25,7 +26,7 @@ body {
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
float: left;
|
||||
margin-right: 15px;
|
||||
margin: 0 15px 0 0;
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
@@ -46,6 +47,7 @@ body {
|
||||
|
||||
ul {
|
||||
float: left;
|
||||
margin: 0;
|
||||
|
||||
&.user-nav {
|
||||
float: right;
|
||||
@@ -72,9 +74,10 @@ body {
|
||||
section.main-content {
|
||||
border-left: 2px solid $dark-blue;
|
||||
@include box-sizing(border-box);
|
||||
width: flex-grid(9);
|
||||
width: flex-grid(9) + flex-gutter();
|
||||
float: left;
|
||||
@include box-shadow( -2px 0 0 darken($light-blue, 3%));
|
||||
@include transition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,229 +0,0 @@
|
||||
html, body, div, span, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
abbr, address, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, samp,
|
||||
small, strong, sub, sup, var,
|
||||
b, i,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin:0;
|
||||
padding:0;
|
||||
border:0;
|
||||
outline:0;
|
||||
vertical-align:baseline;
|
||||
background:transparent;
|
||||
}
|
||||
|
||||
html,body {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
// Corrects block display not defined in IE8/9 & FF3
|
||||
article, aside, details, figcaption, figure, footer, header, hgroup, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
// Corrects inline-block display not defined in IE8/9 & FF3
|
||||
audio, canvas, video {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
// Prevents modern browsers from displaying 'audio' without controls
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Addresses styling for 'hidden' attribute not present in IE8/9, FF3, S4
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Prevents iOS text size adjust after orientation change, without disabling user zoom
|
||||
// www.456bereastreet.com/archive/201012/controlling_text_size_in_safari_for_ios_without_disabling_user_zoom/
|
||||
html {
|
||||
font-size: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
// Addresses font-family inconsistency between 'textarea' and other form elements.
|
||||
html, button, input, select, textarea {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
// Addresses outline displayed oddly in Chrome
|
||||
&:focus {
|
||||
outline: thin dotted;
|
||||
// Webkit
|
||||
outline: 5px auto -webkit-focus-ring-color;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
// Improves readability when focused and also mouse hovered in all browsers
|
||||
// people.opera.com/patrickl/experiments/keyboard/test
|
||||
&:hover, &:active {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Addresses styling not present in IE8/9, S5, Chrome
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
|
||||
// Addresses style set to 'bolder' in FF3+, S4/5, Chrome
|
||||
b, strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
// Addresses styling not present in S5, Chrome
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
// Addresses styling not present in IE8/9
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
// Corrects font family set oddly in S4/5, Chrome
|
||||
// en.wikipedia.org/wiki/User:Davidgothberg/Test59
|
||||
pre, code, kbd, samp {
|
||||
font-family: monospace, serif;
|
||||
_font-family: 'courier new', monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
// Improves readability of pre-formatted text in all browsers
|
||||
pre {
|
||||
white-space: pre;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
// Addresses quote property not supported in S4
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
&:before, &:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
sub, sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
nav {
|
||||
ul, ol {
|
||||
list-style: none;
|
||||
list-style-image: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Removes border when inside 'a' element in IE8/9, FF3
|
||||
img {
|
||||
border: 0;
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
|
||||
// Corrects overflow displayed oddly in IE9
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// Define consistent border, margin, and padding
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
legend {
|
||||
border: 0; // Corrects color not being inherited in IE8/9
|
||||
padding: 0;
|
||||
white-space: normal; // Corrects text not wrapping in FF3
|
||||
}
|
||||
|
||||
button, input, select, textarea {
|
||||
font-size: 100%; // Corrects font size not being inherited in all browsers
|
||||
margin: 0; // Addresses margins set differently in FF3+, S5, Chrome
|
||||
vertical-align: baseline; // Improves appearance and consistency in all browsers
|
||||
}
|
||||
|
||||
// Addresses FF3/4 setting line-height on 'input' using !important in the UA stylesheet
|
||||
button, input {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
button, input[type="button"], input[type="reset"], input[type="submit"] {
|
||||
cursor: pointer; // Improves usability and consistency of cursor style between image-type 'input' and others
|
||||
-webkit-appearance: button; // Corrects inability to style clickable 'input' types in iOS
|
||||
}
|
||||
|
||||
// Re-set default cursor for disabled elements
|
||||
button[disabled], input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
input[type="checkbox"], input[type="radio"] {
|
||||
box-sizing: border-box; // Addresses box sizing set to content-box in IE8/9
|
||||
padding: 0; //Removes excess padding in IE8/9
|
||||
}
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; // Addresses appearance set to searchfield in S5, Chrome
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box; // Addresses box-sizing set to border-box in S5, Chrome (-moz to future-proof)
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
// Removes inner padding and search cancel button in S5, Chrome on OS X
|
||||
input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
// Removes inner padding and border in FF3+
|
||||
// www.sitepen.com/blog/2008/05/14/the-devils-in-the-details-fixing-dojos-toolbar-buttons/
|
||||
button::-moz-focus-inner, input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto; // Removes default vertical scrollbar in IE8/9
|
||||
vertical-align: top; // Improves readability and alignment in all browsers
|
||||
}
|
||||
|
||||
// Remove most spacing between table cells
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
@@ -1,24 +1,48 @@
|
||||
section#unit-wrapper {
|
||||
section.filters {
|
||||
@include clearfix;
|
||||
margin-bottom: 10px;
|
||||
opacity: .4;
|
||||
margin-bottom: 10px;
|
||||
@include transition;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@include inline-block();
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
font-size: 14px;
|
||||
padding: 6px 6px 6px 0;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
@include clearfix();
|
||||
list-style: none;
|
||||
padding: 6px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
@include inline-block();
|
||||
@include inline-block;
|
||||
margin-right: 6px;
|
||||
border-right: 1px solid #ddd;
|
||||
padding-right: 6px;
|
||||
|
||||
&.advanced {
|
||||
&.search {
|
||||
float: right;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
&.more {
|
||||
font-size: 12px;
|
||||
@include inline-block;
|
||||
margin: 0 6px;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,11 +63,13 @@ section#unit-wrapper {
|
||||
@include clearfix;
|
||||
|
||||
h2 {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
font-size: 12px;
|
||||
float: left;
|
||||
color: $bright-blue;
|
||||
float: left;
|
||||
font-size: 12px;
|
||||
letter-spacing: 1px;
|
||||
line-height: 19px;
|
||||
text-transform: uppercase;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +92,8 @@ section#unit-wrapper {
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid lighten($dark-blue, 60%);
|
||||
@@ -76,15 +104,20 @@ section#unit-wrapper {
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
padding: 6px;
|
||||
position: relative;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($yellow, 10%);
|
||||
|
||||
a.draggable {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -92,7 +125,7 @@ section#unit-wrapper {
|
||||
|
||||
a.draggable {
|
||||
float: right;
|
||||
opacity: .5;
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
&.group {
|
||||
@@ -104,13 +137,15 @@ section#unit-wrapper {
|
||||
|
||||
h3 {
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ol {
|
||||
border-left: 4px solid #999;
|
||||
border-bottom: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
&:last-child {
|
||||
@@ -133,31 +168,52 @@ section#unit-wrapper {
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid darken($light-blue, 8%);
|
||||
background: lighten($light-blue, 2%);
|
||||
background: $light-blue;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&.new-module a {
|
||||
background-color: darken($light-blue, 2%);
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($yellow, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: $dark-blue;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
padding: 6px;
|
||||
border-collapse: collapse;
|
||||
position: relative;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
border-bottom: 1px solid darken($light-blue, 8%);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($yellow, 10%);
|
||||
|
||||
a.draggable {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.empty {
|
||||
padding: 12px;
|
||||
|
||||
@@ -169,16 +225,10 @@ section#unit-wrapper {
|
||||
}
|
||||
|
||||
a.draggable {
|
||||
float: right;
|
||||
opacity: .3;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,19 @@ section#unit-wrapper {
|
||||
letter-spacing: 1px;
|
||||
@include inline-block();
|
||||
color: $bright-blue;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
@include inline-block();
|
||||
margin-left: 10px;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
margin: 0;
|
||||
|
||||
a {
|
||||
text-indent: -9999px;
|
||||
@include inline-block();
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +46,7 @@ section#unit-wrapper {
|
||||
|
||||
&.save-update {
|
||||
@extend .button;
|
||||
margin: -6px -25px -6px 0;
|
||||
margin: -6px -21px -6px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,42 +56,68 @@ section#unit-wrapper {
|
||||
padding: 20px;
|
||||
|
||||
section.meta {
|
||||
background: $light-blue;
|
||||
border-bottom: 1px solid lighten($dark-blue, 40%);
|
||||
padding: 10px 20px;
|
||||
margin: -20px -20px 10px;
|
||||
opacity: .7;
|
||||
@include transition;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
padding: 20px;
|
||||
margin: -20px -20px 10px;
|
||||
}
|
||||
|
||||
section {
|
||||
&.status-settings {
|
||||
float: left;
|
||||
margin-bottom: 10px;
|
||||
color: $dark-blue;
|
||||
@include clearfix;
|
||||
|
||||
ul {
|
||||
border: 1px solid darken($light-blue, 15%);
|
||||
@include clearfix();
|
||||
float: left;
|
||||
list-style: none;
|
||||
border: 1px solid lighten($dark-blue, 40%);
|
||||
@include inline-block();
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
@include inline-block();
|
||||
border-right: 1px solid lighten($dark-blue, 40%);
|
||||
padding: 6px;
|
||||
border-right: 1px solid darken($light-blue, 15%);
|
||||
float: left;
|
||||
|
||||
&:last-child {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
&.current {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $dark-blue;
|
||||
padding: 6px;
|
||||
display: block;
|
||||
|
||||
&.current {
|
||||
background: darken($light-blue, 5%);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($yellow, 13%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.settings {
|
||||
@include inline-block();
|
||||
float: left;
|
||||
margin: 0 20px;
|
||||
padding: 6px;
|
||||
border: 1px solid lighten($dark-blue, 40%);
|
||||
border: 1px solid darken($light-blue, 15%);
|
||||
color: $dark-blue;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($yellow, 13%);
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
@@ -110,10 +141,7 @@ section#unit-wrapper {
|
||||
}
|
||||
|
||||
&.tags {
|
||||
background: $light-blue;
|
||||
color: lighten($dark-blue, 6%);
|
||||
padding: 10px;
|
||||
margin: 0 0 20px;
|
||||
@include clearfix();
|
||||
clear: both;
|
||||
|
||||
@@ -124,10 +152,12 @@ section#unit-wrapper {
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
@include inline-block();
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
@include inline-block();
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,6 +222,8 @@ section#unit-wrapper {
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
margin-bottom: 20px;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@import 'bourbon/bourbon';
|
||||
@import 'reset';
|
||||
@import 'vendor/normalize';
|
||||
|
||||
@import 'base', 'layout';
|
||||
@import 'base', 'layout', 'content-types';
|
||||
@import 'calendar';
|
||||
@import 'section', 'unit';
|
||||
|
||||
504
cms/static/sass/vendor/_normalize.scss
vendored
Normal file
@@ -0,0 +1,504 @@
|
||||
/*! normalize.css 2012-03-11T12:53 UTC - http://github.com/necolas/normalize.css */
|
||||
|
||||
/* =============================================================================
|
||||
HTML5 display definitions
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Corrects block display not defined in IE6/7/8/9 & FF3
|
||||
*/
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Corrects inline-block display not defined in IE6/7/8/9 & FF3
|
||||
*/
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
video {
|
||||
display: inline-block;
|
||||
*display: inline;
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prevents modern browsers from displaying 'audio' without controls
|
||||
* Remove excess height in iOS5 devices
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling for 'hidden' attribute not present in IE7/8/9, FF3, S4
|
||||
* Known issue: no IE6 support
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Base
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* 1. Corrects text resizing oddly in IE6/7 when body font-size is set using em units
|
||||
* http://clagnut.com/blog/348/#c790
|
||||
* 2. Prevents iOS text size adjust after orientation change, without disabling user zoom
|
||||
* www.456bereastreet.com/archive/201012/controlling_text_size_in_safari_for_ios_without_disabling_user_zoom/
|
||||
*/
|
||||
|
||||
html {
|
||||
font-size: 100%; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses font-family inconsistency between 'textarea' and other form elements.
|
||||
*/
|
||||
|
||||
html,
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses margins handled incorrectly in IE6/7
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Links
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses outline displayed oddly in Chrome
|
||||
*/
|
||||
|
||||
a:focus {
|
||||
outline: thin dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
* Improves readability when focused and also mouse hovered in all browsers
|
||||
* people.opera.com/patrickl/experiments/keyboard/test
|
||||
*/
|
||||
|
||||
a:hover,
|
||||
a:active {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Typography
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses font sizes and margins set differently in IE6/7
|
||||
* Addresses font sizes within 'section' and 'article' in FF4+, Chrome, S5
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5em;
|
||||
margin: 0.83em 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.17em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1em;
|
||||
margin: 1.33em 0;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 0.83em;
|
||||
margin: 1.67em 0;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 0.75em;
|
||||
margin: 2.33em 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling not present in IE7/8/9, S5, Chrome
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses style set to 'bolder' in FF3+, S4/5, Chrome
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling not present in S5, Chrome
|
||||
*/
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling not present in IE6/7/8/9
|
||||
*/
|
||||
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses margins set differently in IE6/7
|
||||
*/
|
||||
|
||||
p,
|
||||
pre {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Corrects font family set oddly in IE6, S4/5, Chrome
|
||||
* en.wikipedia.org/wiki/User:Davidgothberg/Test59
|
||||
*/
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, serif;
|
||||
_font-family: 'courier new', monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/*
|
||||
* Improves readability of pre-formatted text in all browsers
|
||||
*/
|
||||
|
||||
pre {
|
||||
white-space: pre;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Addresses CSS quotes not supported in IE6/7
|
||||
* 2. Addresses quote property not supported in S4
|
||||
*/
|
||||
|
||||
/* 1 */
|
||||
|
||||
q {
|
||||
quotes: none;
|
||||
}
|
||||
|
||||
/* 2 */
|
||||
|
||||
q:before,
|
||||
q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prevents sub and sup affecting line-height in all browsers
|
||||
* gist.github.com/413930
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Lists
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses margins set differently in IE6/7
|
||||
*/
|
||||
|
||||
dl,
|
||||
menu,
|
||||
ol,
|
||||
ul {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin: 0 0 0 40px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses paddings set differently in IE6/7
|
||||
*/
|
||||
|
||||
menu,
|
||||
ol,
|
||||
ul {
|
||||
padding: 0 0 0 40px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Corrects list images handled incorrectly in IE7
|
||||
*/
|
||||
|
||||
nav ul,
|
||||
nav ol {
|
||||
list-style: none;
|
||||
list-style-image: none;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* 1. Removes border when inside 'a' element in IE6/7/8/9, FF3
|
||||
* 2. Improves image quality when scaled in IE7
|
||||
* code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/
|
||||
*/
|
||||
|
||||
img {
|
||||
border: 0; /* 1 */
|
||||
-ms-interpolation-mode: bicubic; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Corrects overflow displayed oddly in IE9
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Figures
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses margin not present in IE6/7/8/9, S5, O11
|
||||
*/
|
||||
|
||||
figure {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Forms
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Corrects margin displayed oddly in IE6/7
|
||||
*/
|
||||
|
||||
form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Define consistent border, margin, and padding
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Corrects color not being inherited in IE6/7/8/9
|
||||
* 2. Corrects text not wrapping in FF3
|
||||
* 3. Corrects alignment displayed oddly in IE6/7
|
||||
*/
|
||||
|
||||
legend {
|
||||
border: 0; /* 1 */
|
||||
padding: 0;
|
||||
white-space: normal; /* 2 */
|
||||
*margin-left: -7px; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Corrects font size not being inherited in all browsers
|
||||
* 2. Addresses margins set differently in IE6/7, FF3+, S5, Chrome
|
||||
* 3. Improves appearance and consistency in all browsers
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font-size: 100%; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
vertical-align: baseline; /* 3 */
|
||||
*vertical-align: middle; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses FF3/4 setting line-height on 'input' using !important in the UA stylesheet
|
||||
*/
|
||||
|
||||
button,
|
||||
input {
|
||||
line-height: normal; /* 1 */
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Improves usability and consistency of cursor style between image-type 'input' and others
|
||||
* 2. Corrects inability to style clickable 'input' types in iOS
|
||||
* 3. Removes inner spacing in IE7 without affecting normal text inputs
|
||||
* Known issue: inner spacing remains in IE6
|
||||
*/
|
||||
|
||||
button,
|
||||
input[type="button"],
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
cursor: pointer; /* 1 */
|
||||
-webkit-appearance: button; /* 2 */
|
||||
*overflow: visible; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Re-set default cursor for disabled elements
|
||||
*/
|
||||
|
||||
button[disabled],
|
||||
input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Addresses box sizing set to content-box in IE8/9
|
||||
* 2. Removes excess padding in IE8/9
|
||||
* 3. Removes excess padding in IE7
|
||||
Known issue: excess padding remains in IE6
|
||||
*/
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
*height: 13px; /* 3 */
|
||||
*width: 13px; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Addresses appearance set to searchfield in S5, Chrome
|
||||
* 2. Addresses box-sizing set to border-box in S5, Chrome (include -moz to future-proof)
|
||||
*/
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box; /* 2 */
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes inner padding and search cancel button in S5, Chrome on OS X
|
||||
*/
|
||||
|
||||
input[type="search"]::-webkit-search-decoration,
|
||||
input[type="search"]::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes inner padding and border in FF3+
|
||||
* www.sitepen.com/blog/2008/05/14/the-devils-in-the-details-fixing-dojos-toolbar-buttons/
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Removes default vertical scrollbar in IE6/7/8/9
|
||||
* 2. Improves readability and alignment in all browsers
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto; /* 1 */
|
||||
vertical-align: top; /* 2 */
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Tables
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Remove most spacing between table cells
|
||||
*/
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
13
cms/templates/course_index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<%inherit file="base.html" />
|
||||
<%block name="title">Course Manager</%block>
|
||||
|
||||
<%block name="content">
|
||||
<section class="main-container">
|
||||
|
||||
<%include file="widgets/navigation.html"/>
|
||||
|
||||
<section class="main-content">
|
||||
</section>
|
||||
|
||||
</section>
|
||||
</%block>
|
||||
@@ -1,13 +1,12 @@
|
||||
<%inherit file="base.html" />
|
||||
<%block name="title">Course Manager</%block>
|
||||
<%block name="title">Courses</%block>
|
||||
|
||||
<%block name="content">
|
||||
<section class="main-container">
|
||||
|
||||
<%include file="widgets/navigation.html"/>
|
||||
|
||||
<section class="main-content">
|
||||
</section>
|
||||
|
||||
<ol>
|
||||
%for course, url in courses:
|
||||
<li><a href="${url}">${course}</a></li>
|
||||
%endfor
|
||||
</ol>
|
||||
</section>
|
||||
</%block>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<header>
|
||||
<section>
|
||||
<h1 class="editable">${name}</h1>
|
||||
<p>${category}</p>
|
||||
<p class="${category}"><a href="#">${category}</a></p>
|
||||
</section>
|
||||
|
||||
<div class="actions">
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
<h2><a href="/">6.002x circuits and electronics</a></h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#" class="new-module">New Section</a>
|
||||
<a href="#" class="new-module wip">New Module</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="new-module">New Unit</a>
|
||||
<a href="#" class="new-module wip">New Unit</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="user-nav">
|
||||
<li><a href="#">Tasks</a></li>
|
||||
<li><a href="#">Settings</a></li>
|
||||
<li><a href="#" class="wip">Tasks</a></li>
|
||||
<li><a href="#" class="wip">Settings</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<textarea name="" class="edit-box" rows="8" cols="40">${data}</textarea>
|
||||
<div class="preview">${data}</div>
|
||||
|
||||
<div class="actions wip">
|
||||
<div class="actions">
|
||||
<a href="" class="save-update">Save & Update</a>
|
||||
<a href="#" class="cancel">Cancel</a>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<a href="#" class="new-module">
|
||||
+ Add new module
|
||||
</a>
|
||||
<div>
|
||||
|
||||
<div class="hidden">
|
||||
<form>
|
||||
<ul>
|
||||
<li>
|
||||
|
||||
@@ -1,35 +1,38 @@
|
||||
<section class="cal">
|
||||
<header class="wip">
|
||||
<h2>Filter content:</h2>
|
||||
<ul class="actions">
|
||||
<li><a href="#">Timeline view</a></li>
|
||||
<li><a href="#">Multi-Module edit</a></li>
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#">Sequences</a>
|
||||
|
||||
<ul>
|
||||
<li>Hide all</li>
|
||||
<li>Lectures</li>
|
||||
<li>Labs</li>
|
||||
<li>Homeworks</li>
|
||||
<li>Exams</li>
|
||||
</ul>
|
||||
<h2>Sort:</h2>
|
||||
<select>
|
||||
<option value="">Linear Order</option>
|
||||
<option value="">Recently Modified</option>
|
||||
<option value="">Type</option>
|
||||
<option value="">Alphabetically</option>
|
||||
</select>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="#">Deadlines</a>
|
||||
|
||||
<ul>
|
||||
<li>Today</li>
|
||||
<li>Tomorrow</li>
|
||||
<li>This week</li>
|
||||
<li>In 2 weeks</li>
|
||||
<li>This month</li>
|
||||
</ul>
|
||||
<h2>Filter:</h2>
|
||||
<select>
|
||||
<option value="">All content</option>
|
||||
<option value="">Videos</option>
|
||||
<option value="">Problems</option>
|
||||
<option value="">Labs</option>
|
||||
<option value="">Tutorials</option>
|
||||
<option value="">HTML</option>
|
||||
</select>
|
||||
<a href="#" class="more">More</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">Goals</a>
|
||||
<ul>
|
||||
<li>Hide</li>
|
||||
</ul>
|
||||
<a href="#">Hide goals</a>
|
||||
</li>
|
||||
<li class="search">
|
||||
<input type="search" name="" id="" value="" placeholder="Search" />
|
||||
</li>
|
||||
</ul>
|
||||
</header>
|
||||
@@ -61,7 +64,7 @@
|
||||
</ul>
|
||||
</li>
|
||||
%endfor
|
||||
<li>
|
||||
<li class="wip">
|
||||
<header>
|
||||
<h1>Course Scratch Pad</h1>
|
||||
</header>
|
||||
@@ -83,14 +86,15 @@
|
||||
<a href="" class="video-edit">Video 3</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
<%include file="module-dropdown.html"/>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<section class="new-section">
|
||||
<a href="#" >+ Add New Section</a>
|
||||
<a href="#" class="wip" >+ Add New Section</a>
|
||||
|
||||
<section>
|
||||
<section class="hidden">
|
||||
<form>
|
||||
<ul>
|
||||
<li>
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
<section class="sequence-edit">
|
||||
<section class="filters">
|
||||
<section class="filters wip">
|
||||
<ul>
|
||||
<li>
|
||||
<label for="">Sort by</label>
|
||||
<h2>Sort:</h2>
|
||||
<select>
|
||||
<option value="">Linear Order</option>
|
||||
<option value="">Recently Modified</option>
|
||||
<option value="">Type</option>
|
||||
<option value="">Alphabetically</option>
|
||||
</select>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<label for="">Display</label>
|
||||
<h2>Filter:</h2>
|
||||
<select>
|
||||
<option value="">All content</option>
|
||||
<option value="">Videos</option>
|
||||
<option value="">Problems</option>
|
||||
<option value="">Labs</option>
|
||||
<option value="">Tutorials</option>
|
||||
<option value="">HTML</option>
|
||||
</select>
|
||||
<a href="#" class="more">More</a>
|
||||
</li>
|
||||
<li>
|
||||
<select>
|
||||
<option value="">Internal Only</option>
|
||||
</select>
|
||||
</li>
|
||||
|
||||
<li class="advanced">
|
||||
<a href="#">Advanced filters</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<input type="search" name="" id="" value="" />
|
||||
<li class="search">
|
||||
<input type="search" name="" id="" value="" placeholder="Search" />
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
@@ -36,7 +35,7 @@
|
||||
<li>
|
||||
<ol>
|
||||
% for child in module.get_children():
|
||||
<li>
|
||||
<li class="${module.category}">
|
||||
<a href="#" class="module-edit" data-id="${child.location.url()}" data-type="${child.js_module_name()}">${child.name}</a>
|
||||
<a href="#" class="draggable">handle</a>
|
||||
</li>
|
||||
@@ -47,7 +46,7 @@
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section class="scratch-pad">
|
||||
<section class="scratch-pad wip">
|
||||
<ol>
|
||||
<li class="new-module">
|
||||
<%include file="new-module.html"/>
|
||||
|
||||
@@ -9,7 +9,9 @@ urlpatterns = ('',
|
||||
url(r'^$', 'contentstore.views.index', name='index'),
|
||||
url(r'^edit_item$', 'contentstore.views.edit_item', name='edit_item'),
|
||||
url(r'^save_item$', 'contentstore.views.save_item', name='save_item'),
|
||||
url(r'^temp_force_export$', 'contentstore.views.temp_force_export')
|
||||
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<name>[^/]+)$', 'contentstore.views.course_index', name='course_index'),
|
||||
url(r'^temp_force_export$', 'contentstore.views.temp_force_export'),
|
||||
url(r'^github_service_hook$', 'github_sync.views.github_post_receive'),
|
||||
)
|
||||
|
||||
if settings.DEBUG:
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
#! /bin/bash
|
||||
|
||||
cd $(dirname $0) && django-admin.py collectstatic --noinput --settings=envs.aws --pythonpath=.
|
||||
@@ -251,7 +251,7 @@ class LoncapaProblem(object):
|
||||
if file is not None:
|
||||
try:
|
||||
ifp = self.system.filestore.open(file) # open using I4xSystem OSFS filestore
|
||||
except Exception,err:
|
||||
except Exception as err:
|
||||
log.error('Error %s in problem xml include: %s' % (err,etree.tostring(inc,pretty_print=True)))
|
||||
log.error('Cannot find file %s in %s' % (file,self.system.filestore))
|
||||
if not self.system.get('DEBUG'): # if debugging, don't fail - just log error
|
||||
@@ -259,7 +259,7 @@ class LoncapaProblem(object):
|
||||
else: continue
|
||||
try:
|
||||
incxml = etree.XML(ifp.read()) # read in and convert to XML
|
||||
except Exception,err:
|
||||
except Exception as err:
|
||||
log.error('Error %s in problem xml include: %s' % (err,etree.tostring(inc,pretty_print=True)))
|
||||
log.error('Cannot parse XML in %s' % (file))
|
||||
if not self.system.get('DEBUG'): # if debugging, don't fail - just log error
|
||||
@@ -283,6 +283,7 @@ class LoncapaProblem(object):
|
||||
context.update(global_context) # initialize context to have stuff in global_context
|
||||
context['__builtins__'] = globals()['__builtins__'] # put globals there also
|
||||
context['the_lcp'] = self # pass instance of LoncapaProblem in
|
||||
context['script_code'] = ''
|
||||
|
||||
for script in tree.findall('.//script'):
|
||||
stype = script.get('type')
|
||||
@@ -295,10 +296,12 @@ class LoncapaProblem(object):
|
||||
code = script.text
|
||||
XMLESC = {"'": "'", """: '"'}
|
||||
code = unescape(code, XMLESC)
|
||||
context['script_code'] += code # store code source in context
|
||||
try:
|
||||
exec code in context, context # use "context" for global context; thus defs in code are global within code
|
||||
exec code in context, context # use "context" for global context; thus defs in code are global within code
|
||||
except Exception:
|
||||
log.exception("Error while execing code: " + code)
|
||||
log.exception("Error while execing script code: " + code)
|
||||
raise responsetypes.LoncapaProblemError("Error while executing script code")
|
||||
return context
|
||||
|
||||
def _extract_html(self, problemtree): # private
|
||||
@@ -311,6 +314,10 @@ class LoncapaProblem(object):
|
||||
|
||||
Used by get_html.
|
||||
'''
|
||||
if problemtree.tag=='script' and problemtree.get('type') and 'javascript' in problemtree.get('type'):
|
||||
# leave javascript intact.
|
||||
return problemtree
|
||||
|
||||
if problemtree.tag in html_problem_semantics:
|
||||
return
|
||||
|
||||
@@ -22,10 +22,14 @@ Each input type takes the xml tree as 'element', the previous answer as 'value',
|
||||
# status is currently the answer for the problem ID for the input element,
|
||||
# but it will turn into a dict containing both the answer and any associated message for the problem ID for the input element.
|
||||
|
||||
import logging
|
||||
import re
|
||||
import shlex # for splitting quoted strings
|
||||
|
||||
from lxml import etree
|
||||
import xml.sax.saxutils as saxutils
|
||||
|
||||
log = logging.getLogger('mitx.' + __name__)
|
||||
|
||||
def get_input_xml_tags():
|
||||
''' Eventually, this will be for all registered input types '''
|
||||
@@ -121,7 +125,7 @@ def optioninput(element, value, status, render_template, msg=''):
|
||||
eid=element.get('id')
|
||||
options = element.get('options')
|
||||
if not options:
|
||||
raise Exception,"[courseware.capa.inputtypes.optioninput] Missing options specification in " + etree.tostring(element)
|
||||
raise Exception("[courseware.capa.inputtypes.optioninput] Missing options specification in " + etree.tostring(element))
|
||||
oset = shlex.shlex(options[1:-1])
|
||||
oset.quotes = "'"
|
||||
oset.whitespace = ","
|
||||
@@ -159,10 +163,11 @@ def choicegroup(element, value, status, render_template, msg=''):
|
||||
choices={}
|
||||
for choice in element:
|
||||
if not choice.tag=='choice':
|
||||
raise Exception,"[courseware.capa.inputtypes.choicegroup] Error only <choice> tags should be immediate children of a <choicegroup>, found %s instead" % choice.tag
|
||||
raise Exception("[courseware.capa.inputtypes.choicegroup] Error only <choice> tags should be immediate children of a <choicegroup>, found %s instead" % choice.tag)
|
||||
ctext = ""
|
||||
ctext += ''.join([etree.tostring(x) for x in choice]) # TODO: what if choice[0] has math tags in it?
|
||||
ctext += choice.text # TODO: fix order?
|
||||
if choice.text is not None:
|
||||
ctext += choice.text # TODO: fix order?
|
||||
choices[choice.get("name")] = ctext
|
||||
context={'id':eid, 'value':value, 'state':status, 'type':type, 'choices':choices}
|
||||
html = render_template("choicegroup.html", context)
|
||||
@@ -182,9 +187,18 @@ def textline(element, value, status, render_template, msg=""):
|
||||
raise Exception(msg)
|
||||
count = int(eid.split('_')[-2])-1 # HACK
|
||||
size = element.get('size')
|
||||
context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size, 'msg': msg}
|
||||
hidden = element.get('hidden','') # if specified, then textline is hidden and id is stored in div of name given by hidden
|
||||
escapedict = {'"': '"'}
|
||||
value = saxutils.escape(value,escapedict) # otherwise, answers with quotes in them crashes the system!
|
||||
context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size, 'msg': msg, 'hidden': hidden}
|
||||
html = render_template("textinput.html", context)
|
||||
return etree.XML(html)
|
||||
try:
|
||||
xhtml = etree.XML(html)
|
||||
except Exception as err:
|
||||
if True: # TODO needs to be self.system.DEBUG - but can't access system
|
||||
log.debug('[inputtypes.textline] failed to parse XML for:\n%s' % html)
|
||||
raise
|
||||
return xhtml
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
@@ -195,7 +209,6 @@ def textline_dynamath(element, value, status, render_template, msg=''):
|
||||
'''
|
||||
# TODO: Make a wrapper for <formulainput>
|
||||
# TODO: Make an AJAX loop to confirm equation is okay in real-time as user types
|
||||
## TODO: Code should follow PEP8 (4 spaces per indentation level)
|
||||
'''
|
||||
textline is used for simple one-line inputs, like formularesponse and symbolicresponse.
|
||||
uses a <span id=display_eid>`{::}`</span>
|
||||
@@ -204,8 +217,9 @@ def textline_dynamath(element, value, status, render_template, msg=''):
|
||||
eid=element.get('id')
|
||||
count = int(eid.split('_')[-2])-1 # HACK
|
||||
size = element.get('size')
|
||||
hidden = element.get('hidden','') # if specified, then textline is hidden and id is stored in div of name given by hidden
|
||||
context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size,
|
||||
'msg':msg,
|
||||
'msg':msg, 'hidden' : hidden,
|
||||
}
|
||||
html = render_template("textinput_dynamath.html", context)
|
||||
return etree.XML(html)
|
||||
@@ -225,14 +239,24 @@ def textbox(element, value, status, render_template, msg=''):
|
||||
rows = element.get('rows') or '30'
|
||||
cols = element.get('cols') or '80'
|
||||
mode = element.get('mode') or 'python' # mode for CodeMirror, eg "python" or "xml"
|
||||
hidden = element.get('hidden','') # if specified, then textline is hidden and id is stored in div of name given by hidden
|
||||
linenumbers = element.get('linenumbers') # for CodeMirror
|
||||
if not value: value = element.text # if no student input yet, then use the default input given by the problem
|
||||
context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size, 'msg':msg,
|
||||
'mode':mode, 'linenumbers':linenumbers,
|
||||
'rows':rows, 'cols':cols,
|
||||
'hidden' : hidden,
|
||||
}
|
||||
html = render_template("textbox.html", context)
|
||||
return etree.XML(html)
|
||||
try:
|
||||
xhtml = etree.XML(html)
|
||||
except Exception as err:
|
||||
newmsg = 'error %s in rendering message' % (str(err).replace('<','<'))
|
||||
newmsg += '<br/>Original message: %s' % msg.replace('<','<')
|
||||
context['msg'] = newmsg
|
||||
html = render_template("textbox.html", context)
|
||||
xhtml = etree.XML(html)
|
||||
return xhtml
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
@register_render_function
|
||||
@@ -288,8 +312,18 @@ def math(element, value, status, render_template, msg=''):
|
||||
# isinline = True
|
||||
# html = render_template("mathstring.html",{'mathstr':mathstr,'isinline':isinline,'tail':element.tail})
|
||||
|
||||
html = '<html><html>%s</html><html>%s</html></html>' % (mathstr,element.tail)
|
||||
xhtml = etree.XML(html)
|
||||
html = '<html><html>%s</html><html>%s</html></html>' % (mathstr,saxutils.escape(element.tail))
|
||||
try:
|
||||
xhtml = etree.XML(html)
|
||||
except Exception as err:
|
||||
if False: # TODO needs to be self.system.DEBUG - but can't access system
|
||||
msg = "<html><font color='red'><p>Error %s</p>" % str(err).replace('<','<')
|
||||
msg += '<p>Failed to construct math expression from <pre>%s</pre></p>' % html.replace('<','<')
|
||||
msg += "</font></html>"
|
||||
log.error(msg)
|
||||
return etree.XML(msg)
|
||||
else:
|
||||
raise
|
||||
# xhtml.tail = element.tail # don't forget to include the tail!
|
||||
return xhtml
|
||||
|
||||
@@ -338,14 +372,14 @@ def imageinput(element, value, status, render_template, msg=''):
|
||||
(gx,gy) = (0,0)
|
||||
|
||||
context = {
|
||||
'id':eid,
|
||||
'value':value,
|
||||
'id': eid,
|
||||
'value': value,
|
||||
'height': height,
|
||||
'width' : width,
|
||||
'src':src,
|
||||
'gx':gx,
|
||||
'gy':gy,
|
||||
'state' : status, # to change
|
||||
'width': width,
|
||||
'src': src,
|
||||
'gx': gx,
|
||||
'gy': gy,
|
||||
'state': status, # to change
|
||||
'msg': msg, # to change
|
||||
}
|
||||
html = render_template("imageinput.html", context)
|
||||
@@ -193,7 +193,7 @@ class LoncapaResponse(object):
|
||||
|
||||
try:
|
||||
self.context[hintfn](self.answer_ids, student_answers, new_cmap, old_cmap)
|
||||
except Exception, err:
|
||||
except Exception as err:
|
||||
msg = 'Error %s in evaluating hint function %s' % (err,hintfn)
|
||||
msg += "\nSee XML source line %s" % getattr(self.xml,'sourceline','<unavailable>')
|
||||
raise ResponseError(msg)
|
||||
@@ -556,7 +556,7 @@ def sympy_check2():
|
||||
idset = sorted(self.answer_ids) # ordered list of answer id's
|
||||
try:
|
||||
submission = [student_answers[k] for k in idset] # ordered list of answers
|
||||
except Exception, err:
|
||||
except Exception as err:
|
||||
msg = '[courseware.capa.responsetypes.customresponse] error getting student answer from %s' % student_answers
|
||||
msg += '\n idset = %s, error = %s' % (idset,err)
|
||||
log.error(msg)
|
||||
@@ -567,7 +567,7 @@ def sympy_check2():
|
||||
|
||||
# if there is only one box, and it's empty, then don't evaluate
|
||||
if len(idset)==1 and not submission[0]:
|
||||
return {idset[0]:'no_answer_entered'}
|
||||
return CorrectMap(idset[0],'incorrect',msg='<font color="red">No answer entered!</font>')
|
||||
|
||||
correct = ['unknown'] * len(idset)
|
||||
messages = [''] * len(idset)
|
||||
@@ -594,7 +594,7 @@ def sympy_check2():
|
||||
if type(self.code)==str:
|
||||
try:
|
||||
exec self.code in self.context['global_context'], self.context
|
||||
except Exception,err:
|
||||
except Exception as err:
|
||||
print "oops in customresponse (code) error %s" % err
|
||||
print "context = ",self.context
|
||||
print traceback.format_exc()
|
||||
@@ -619,7 +619,7 @@ def sympy_check2():
|
||||
log.debug('nargs=%d, args=%s, kwargs=%s' % (nargs,args,kwargs))
|
||||
|
||||
ret = fn(*args[:nargs],**kwargs)
|
||||
except Exception,err:
|
||||
except Exception as err:
|
||||
log.error("oops in customresponse (cfn) error %s" % err)
|
||||
# print "context = ",self.context
|
||||
log.error(traceback.format_exc())
|
||||
@@ -746,12 +746,20 @@ main()
|
||||
xml = self.xml
|
||||
self.url = xml.get('url') or "http://eecs1.mit.edu:8889/pyloncapa" # FIXME - hardcoded URL
|
||||
|
||||
answer = xml.xpath('//*[@id=$id]//answer',id=xml.get('id'))[0] # FIXME - catch errors
|
||||
answer_src = answer.get('src')
|
||||
if answer_src is not None:
|
||||
self.code = self.system.filesystem.open('src/'+answer_src).read()
|
||||
else:
|
||||
self.code = answer.text
|
||||
# answer = xml.xpath('//*[@id=$id]//answer',id=xml.get('id'))[0] # FIXME - catch errors
|
||||
answer = xml.find('answer')
|
||||
if answer is not None:
|
||||
answer_src = answer.get('src')
|
||||
if answer_src is not None:
|
||||
self.code = self.system.filesystem.open('src/'+answer_src).read()
|
||||
else:
|
||||
self.code = answer.text
|
||||
else: # no <answer> stanza; get code from <script>
|
||||
self.code = self.context['script_code']
|
||||
if not self.code:
|
||||
msg = '%s: Missing answer script code for externalresponse' % unicode(self)
|
||||
msg += "\nSee XML source line %s" % getattr(self.xml,'sourceline','<unavailable>')
|
||||
raise LoncapaProblemError(msg)
|
||||
|
||||
self.tests = xml.get('tests')
|
||||
|
||||
@@ -774,7 +782,7 @@ main()
|
||||
|
||||
try:
|
||||
r = requests.post(self.url,data=payload) # call external server
|
||||
except Exception,err:
|
||||
except Exception as err:
|
||||
msg = 'Error %s - cannot connect to external server url=%s' % (err,self.url)
|
||||
log.error(msg)
|
||||
raise Exception(msg)
|
||||
@@ -786,7 +794,7 @@ main()
|
||||
|
||||
try:
|
||||
rxml = etree.fromstring(r.text) # response is XML; prase it
|
||||
except Exception,err:
|
||||
except Exception as err:
|
||||
msg = 'Error %s - cannot parse response from external server r.text=%s' % (err,r.text)
|
||||
log.error(msg)
|
||||
raise Exception(msg)
|
||||
@@ -798,7 +806,7 @@ main()
|
||||
cmap = CorrectMap()
|
||||
try:
|
||||
submission = [student_answers[k] for k in idset]
|
||||
except Exception,err:
|
||||
except Exception as err:
|
||||
log.error('Error %s: cannot get student answer for %s; student_answers=%s' % (err,self.answer_ids,student_answers))
|
||||
raise Exception(err)
|
||||
|
||||
@@ -808,7 +816,7 @@ main()
|
||||
|
||||
try:
|
||||
rxml = self.do_external_request('get_score',extra_payload)
|
||||
except Exception, err:
|
||||
except Exception as err:
|
||||
log.error('Error %s' % err)
|
||||
if self.system.DEBUG:
|
||||
cmap.set_dict(dict(zip(sorted(self.answer_ids), ['incorrect'] * len(idset) )))
|
||||
@@ -838,7 +846,7 @@ main()
|
||||
try:
|
||||
rxml = self.do_external_request('get_answers',{})
|
||||
exans = json.loads(rxml.find('expected').text)
|
||||
except Exception,err:
|
||||
except Exception as err:
|
||||
log.error('Error %s' % err)
|
||||
if self.system.DEBUG:
|
||||
msg = '<font color=red size=+2>%s</font>' % str(err).replace('<','<')
|
||||
@@ -935,7 +943,7 @@ class FormulaResponse(LoncapaResponse):
|
||||
except UndefinedVariable as uv:
|
||||
log.debug('formularesponse: undefined variable in given=%s' % given)
|
||||
raise StudentInputError(uv.message+" not permitted in answer")
|
||||
except Exception, err:
|
||||
except Exception as err:
|
||||
#traceback.print_exc()
|
||||
log.debug('formularesponse: error %s in formula' % err)
|
||||
raise StudentInputError("Error in formula")
|
||||
@@ -1,5 +1,9 @@
|
||||
<section id="textbox_${id}" class="textbox">
|
||||
<textarea rows="${rows}" cols="${cols}" name="input_${id}" id="input_${id}">${value|h}</textarea>
|
||||
<textarea rows="${rows}" cols="${cols}" name="input_${id}" id="input_${id}"
|
||||
% if hidden:
|
||||
style="display:none;"
|
||||
% endif
|
||||
>${value|h}</textarea>
|
||||
|
||||
<span id="answer_${id}"></span>
|
||||
|
||||
@@ -12,6 +16,9 @@
|
||||
% elif state == 'incomplete':
|
||||
<span class="incorrect" id="status_${id}"></span>
|
||||
% endif
|
||||
% if hidden:
|
||||
<div style="display:none;" name="${hidden}" inputid="input_${id}" />
|
||||
% endif
|
||||
<br/>
|
||||
<span class="debug">(${state})</span>
|
||||
<br/>
|
||||
@@ -8,11 +8,17 @@
|
||||
% elif state == 'incomplete':
|
||||
<div class="incorrect" id="status_${id}">
|
||||
% endif
|
||||
% if hidden:
|
||||
<div style="display:none;" name="${hidden}" inputid="input_${id}" />
|
||||
% endif
|
||||
|
||||
<input type="text" name="input_${id}" id="input_${id}" value="${value}"
|
||||
% if size:
|
||||
size="${size}"
|
||||
% endif
|
||||
% if hidden:
|
||||
style="display:none;"
|
||||
% endif
|
||||
/>
|
||||
|
||||
<p class="status">
|
||||
@@ -10,9 +10,16 @@
|
||||
<div class="incorrect" id="status_${id}">
|
||||
% elif state == 'incomplete':
|
||||
<div class="incorrect" id="status_${id}">
|
||||
% endif
|
||||
% if hidden:
|
||||
<div style="display:none;" name="${hidden}" inputid="input_${id}" />
|
||||
% endif
|
||||
|
||||
<input type="text" name="input_${id}" id="input_${id}" value="${value}" class="math" size="${size if size else ''}" />
|
||||
<input type="text" name="input_${id}" id="input_${id}" value="${value}" class="math" size="${size if size else ''}"
|
||||
% if hidden:
|
||||
style="display:none;"
|
||||
% endif
|
||||
/>
|
||||
|
||||
<p class="status">
|
||||
% if state == 'unsubmitted':
|
||||
8
common/lib/capa/setup.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="capa",
|
||||
version="0.1",
|
||||
packages=find_packages(exclude=["tests"]),
|
||||
install_requires=['distribute'],
|
||||
)
|
||||
8
common/lib/mitxmako/setup.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="mitxmako",
|
||||
version="0.1",
|
||||
packages=find_packages(exclude=["tests"]),
|
||||
install_requires=['distribute'],
|
||||
)
|
||||
@@ -8,6 +8,10 @@ setup(
|
||||
package_data={
|
||||
'xmodule': ['js/module/*']
|
||||
},
|
||||
requires=[
|
||||
'capa',
|
||||
'mitxmako'
|
||||
],
|
||||
|
||||
# See http://guide.python-distribute.org/creation.html#entry-points
|
||||
# for a description of entry_points
|
||||
|
||||
@@ -135,7 +135,7 @@ class CapaModule(XModule):
|
||||
try:
|
||||
self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(), instance_state, seed=seed, system=self.system)
|
||||
except Exception:
|
||||
msg = 'cannot create LoncapaProblem %s' % self.url
|
||||
msg = 'cannot create LoncapaProblem %s' % self.location.url
|
||||
log.exception(msg)
|
||||
if self.system.DEBUG:
|
||||
msg = '<p>%s</p>' % msg.replace('<', '<')
|
||||
@@ -179,7 +179,12 @@ class CapaModule(XModule):
|
||||
score = d['score']
|
||||
total = d['total']
|
||||
if total > 0:
|
||||
return Progress(score, total)
|
||||
try:
|
||||
return Progress(score, total)
|
||||
except Exception as err:
|
||||
if self.DEBUG:
|
||||
return None
|
||||
raise
|
||||
return None
|
||||
|
||||
def get_html(self):
|
||||
@@ -193,7 +198,18 @@ class CapaModule(XModule):
|
||||
'''Return html for the problem. Adds check, reset, save buttons
|
||||
as necessary based on the problem config and state.'''
|
||||
|
||||
html = self.lcp.get_html()
|
||||
try:
|
||||
html = self.lcp.get_html()
|
||||
except Exception, err:
|
||||
if self.DEBUG:
|
||||
log.exception(err)
|
||||
msg = '[courseware.capa.capa_module] <font size="+1" color="red">Failed to generate HTML for problem %s</font>' % (self.filename)
|
||||
msg += '<p>Error:</p><p><pre>%s</pre></p>' % str(err).replace('<','<')
|
||||
msg += '<p><pre>%s</pre></p>' % traceback.format_exc().replace('<','<')
|
||||
html = msg
|
||||
else:
|
||||
raise
|
||||
|
||||
content = {'name': self.metadata['display_name'],
|
||||
'html': html,
|
||||
'weight': self.weight,
|
||||
@@ -394,14 +410,18 @@ class CapaModule(XModule):
|
||||
correct_map = self.lcp.grade_answers(answers)
|
||||
except StudentInputError as inst:
|
||||
# TODO (vshnayder): why is this line here?
|
||||
self.lcp = LoncapaProblem(self.definition['data'],
|
||||
id=lcp_id, state=old_state, system=self.system)
|
||||
#self.lcp = LoncapaProblem(self.definition['data'],
|
||||
# id=lcp_id, state=old_state, system=self.system)
|
||||
traceback.print_exc()
|
||||
return {'success': inst.message}
|
||||
except:
|
||||
except Exception, err:
|
||||
# TODO: why is this line here?
|
||||
self.lcp = LoncapaProblem(self.definition['data'],
|
||||
id=lcp_id, state=old_state, system=self.system)
|
||||
#self.lcp = LoncapaProblem(self.definition['data'],
|
||||
# id=lcp_id, state=old_state, system=self.system)
|
||||
if self.DEBUG:
|
||||
msg = "Error checking problem: " + str(err)
|
||||
msg += '\nTraceback:\n' + traceback.format_exc()
|
||||
return {'success':msg}
|
||||
traceback.print_exc()
|
||||
raise Exception("error in capa_module")
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
from lxml import etree
|
||||
|
||||
from xmodule.x_module import XModule
|
||||
from xmodule.raw_module import RawDescriptor
|
||||
@@ -26,3 +27,8 @@ class HtmlDescriptor(RawDescriptor):
|
||||
|
||||
js = {'coffee': [resource_string(__name__, 'js/module/html.coffee')]}
|
||||
js_module = 'HTML'
|
||||
|
||||
@classmethod
|
||||
def file_to_xml(cls, file_object):
|
||||
parser = etree.HTMLParser()
|
||||
return etree.parse(file_object, parser).getroot()
|
||||
|
||||
@@ -6,6 +6,9 @@ that are stored in a database an accessible using their Location as an identifie
|
||||
import re
|
||||
from collections import namedtuple
|
||||
from .exceptions import InvalidLocationError
|
||||
import logging
|
||||
|
||||
log = logging.getLogger('mitx.' + 'modulestore')
|
||||
|
||||
URL_RE = re.compile("""
|
||||
(?P<tag>[^:]+)://
|
||||
@@ -74,11 +77,13 @@ class Location(_LocationBase):
|
||||
def check_list(list_):
|
||||
for val in list_:
|
||||
if val is not None and INVALID_CHARS.search(val) is not None:
|
||||
log.debug('invalid characters val="%s", list_="%s"' % (val,list_))
|
||||
raise InvalidLocationError(location)
|
||||
|
||||
if isinstance(location, basestring):
|
||||
match = URL_RE.match(location)
|
||||
if match is None:
|
||||
log.debug('location is instance of %s but no URL match' % basestring)
|
||||
raise InvalidLocationError(location)
|
||||
else:
|
||||
groups = match.groupdict()
|
||||
@@ -86,6 +91,7 @@ class Location(_LocationBase):
|
||||
return _LocationBase.__new__(_cls, **groups)
|
||||
elif isinstance(location, (list, tuple)):
|
||||
if len(location) not in (5, 6):
|
||||
log.debug('location has wrong length')
|
||||
raise InvalidLocationError(location)
|
||||
|
||||
if len(location) == 5:
|
||||
@@ -153,6 +159,18 @@ class ModuleStore(object):
|
||||
location is found
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_items(self, location, default_class=None):
|
||||
"""
|
||||
Returns a list of XModuleDescriptor instances for the items
|
||||
that match location. Any element of location that is None is treated
|
||||
as a wildcard that matches any value
|
||||
|
||||
location: Something that can be passed to Location
|
||||
default_class: An XModuleDescriptor subclass to use if no plugin matching the
|
||||
location is found
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
# TODO (cpennington): Replace with clone_item
|
||||
def create_item(self, location, editor):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import pymongo
|
||||
from bson.objectid import ObjectId
|
||||
from importlib import import_module
|
||||
from xmodule.x_module import XModuleDescriptor
|
||||
from xmodule.mako_module import MakoDescriptorSystem
|
||||
@@ -8,6 +9,19 @@ from . import ModuleStore, Location
|
||||
from .exceptions import ItemNotFoundError, InsufficientSpecificationError
|
||||
|
||||
|
||||
# TODO (cpennington): This code currently operates under the assumption that
|
||||
# there is only one revision for each item. Once we start versioning inside the CMS,
|
||||
# that assumption will have to change
|
||||
|
||||
|
||||
def location_to_query(loc):
|
||||
query = {}
|
||||
for key, val in Location(loc).dict().iteritems():
|
||||
if val is not None:
|
||||
query['_id.{key}'.format(key=key)] = val
|
||||
|
||||
return query
|
||||
|
||||
class MongoModuleStore(ModuleStore):
|
||||
"""
|
||||
A Mongodb backed ModuleStore
|
||||
@@ -17,7 +31,6 @@ class MongoModuleStore(ModuleStore):
|
||||
host=host,
|
||||
port=port
|
||||
)[db][collection]
|
||||
self.collection.ensure_index('location')
|
||||
|
||||
# Force mongo to report errors, at the expense of performance
|
||||
self.collection.safe = True
|
||||
@@ -26,6 +39,18 @@ class MongoModuleStore(ModuleStore):
|
||||
class_ = getattr(import_module(module_path), class_name)
|
||||
self.default_class = class_
|
||||
|
||||
# TODO (cpennington): Pass a proper resources_fs to the system
|
||||
self.system = MakoDescriptorSystem(
|
||||
load_item=self.get_item,
|
||||
resources_fs=None,
|
||||
render_template=render_to_string
|
||||
)
|
||||
|
||||
def _load_item(self, item):
|
||||
item['location'] = item['_id']
|
||||
del item['_id']
|
||||
return XModuleDescriptor.load_from_json(item, self.system, self.default_class)
|
||||
|
||||
def get_item(self, location):
|
||||
"""
|
||||
Returns an XModuleDescriptor instance for the item at location.
|
||||
@@ -39,24 +64,26 @@ class MongoModuleStore(ModuleStore):
|
||||
location: Something that can be passed to Location
|
||||
"""
|
||||
|
||||
query = {}
|
||||
for key, val in Location(location).dict().iteritems():
|
||||
if key != 'revision' and val is None:
|
||||
raise InsufficientSpecificationError(location)
|
||||
|
||||
if val is not None:
|
||||
query['location.{key}'.format(key=key)] = val
|
||||
|
||||
item = self.collection.find_one(
|
||||
query,
|
||||
location_to_query(location),
|
||||
sort=[('revision', pymongo.ASCENDING)],
|
||||
)
|
||||
if item is None:
|
||||
raise ItemNotFoundError(location)
|
||||
return self._load_item(item)
|
||||
|
||||
# TODO (cpennington): Pass a proper resources_fs to the system
|
||||
return XModuleDescriptor.load_from_json(
|
||||
item, MakoDescriptorSystem(load_item=self.get_item, resources_fs=None, render_template=render_to_string), self.default_class)
|
||||
def get_items(self, location, default_class=None):
|
||||
print location_to_query(location)
|
||||
items = self.collection.find(
|
||||
location_to_query(location),
|
||||
sort=[('revision', pymongo.ASCENDING)],
|
||||
)
|
||||
|
||||
return [self._load_item(item) for item in items]
|
||||
|
||||
def create_item(self, location):
|
||||
"""
|
||||
@@ -65,7 +92,7 @@ class MongoModuleStore(ModuleStore):
|
||||
location: Something that can be passed to Location
|
||||
"""
|
||||
self.collection.insert({
|
||||
'location': Location(location).dict(),
|
||||
'_id': Location(location).dict(),
|
||||
})
|
||||
|
||||
def update_item(self, location, data):
|
||||
@@ -80,8 +107,9 @@ class MongoModuleStore(ModuleStore):
|
||||
# See http://www.mongodb.org/display/DOCS/Updating for
|
||||
# atomic update syntax
|
||||
self.collection.update(
|
||||
{'location': Location(location).dict()},
|
||||
{'$set': {'definition.data': data}}
|
||||
{'_id': Location(location).dict()},
|
||||
{'$set': {'definition.data': data}},
|
||||
|
||||
)
|
||||
|
||||
def update_children(self, location, children):
|
||||
@@ -96,7 +124,7 @@ class MongoModuleStore(ModuleStore):
|
||||
# See http://www.mongodb.org/display/DOCS/Updating for
|
||||
# atomic update syntax
|
||||
self.collection.update(
|
||||
{'location': Location(location).dict()},
|
||||
{'_id': Location(location).dict()},
|
||||
{'$set': {'definition.children': children}}
|
||||
)
|
||||
|
||||
@@ -112,6 +140,6 @@ class MongoModuleStore(ModuleStore):
|
||||
# See http://www.mongodb.org/display/DOCS/Updating for
|
||||
# atomic update syntax
|
||||
self.collection.update(
|
||||
{'location': Location(location).dict()},
|
||||
{'_id': Location(location).dict()},
|
||||
{'$set': {'metadata': metadata}}
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ from .exceptions import ItemNotFoundError
|
||||
etree.set_default_parser(etree.XMLParser(dtd_validation=False, load_dtd=False,
|
||||
remove_comments=True, remove_blank_text=True))
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log = logging.getLogger('mitx.' + __name__)
|
||||
|
||||
|
||||
class XMLModuleStore(ModuleStore):
|
||||
@@ -35,9 +35,13 @@ class XMLModuleStore(ModuleStore):
|
||||
self.default_class = None
|
||||
else:
|
||||
module_path, _, class_name = default_class.rpartition('.')
|
||||
log.debug('module_path = %s' % module_path)
|
||||
class_ = getattr(import_module(module_path), class_name)
|
||||
self.default_class = class_
|
||||
|
||||
log.debug('XMLModuleStore: eager=%s, data_dir = %s' % (eager,self.data_dir))
|
||||
log.debug('default_class = %s' % self.default_class)
|
||||
|
||||
with open(self.data_dir / "course.xml") as course_file:
|
||||
class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
|
||||
def __init__(self, modulestore):
|
||||
@@ -65,9 +69,11 @@ class XMLModuleStore(ModuleStore):
|
||||
slug = '{slug}_{count}'.format(slug=slug, count=self.unnamed_modules)
|
||||
|
||||
self.used_slugs.add(slug)
|
||||
# log.debug('-> slug=%s' % slug)
|
||||
xml_data.set('slug', slug)
|
||||
|
||||
module = XModuleDescriptor.load_from_xml(etree.tostring(xml_data), self, org, course, modulestore.default_class)
|
||||
log.debug('==> importing module location %s' % repr(module.location))
|
||||
modulestore.modules[module.location] = module
|
||||
|
||||
if eager:
|
||||
@@ -84,6 +90,7 @@ class XMLModuleStore(ModuleStore):
|
||||
XMLParsingSystem.__init__(self, **system_kwargs)
|
||||
|
||||
self.course = ImportSystem(self).process_xml(course_file.read())
|
||||
log.debug('========> Done with course import')
|
||||
|
||||
def get_item(self, location):
|
||||
"""
|
||||
|
||||
@@ -344,6 +344,8 @@ class XModuleDescriptor(Plugin):
|
||||
etree.fromstring(xml_data).tag,
|
||||
default_class
|
||||
)
|
||||
# leave next line in code, commented out - useful for low-level debugging
|
||||
# log.debug('[XModuleDescriptor.load_from_xml] tag=%s, class_=%s' % (etree.fromstring(xml_data).tag,class_))
|
||||
return class_.from_xml(xml_data, system, org, course)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -90,6 +90,16 @@ class XmlDescriptor(XModuleDescriptor):
|
||||
if xml_object.get(attr) is not None:
|
||||
del xml_object.attrib[attr]
|
||||
|
||||
@classmethod
|
||||
def file_to_xml(cls, file_object):
|
||||
"""
|
||||
Used when this module wants to parse a file object to xml
|
||||
that will be converted to the definition.
|
||||
|
||||
Returns an lxml Element
|
||||
"""
|
||||
return etree.parse(file_object).getroot()
|
||||
|
||||
@classmethod
|
||||
def from_xml(cls, xml_data, system, org=None, course=None):
|
||||
"""
|
||||
@@ -125,9 +135,10 @@ class XmlDescriptor(XModuleDescriptor):
|
||||
definition_xml = copy.deepcopy(xml_object)
|
||||
else:
|
||||
filepath = cls._format_filepath(xml_object.tag, filename)
|
||||
log.debug('filepath=%s, resources_fs=%s' % (filepath,system.resources_fs))
|
||||
with system.resources_fs.open(filepath) as file:
|
||||
try:
|
||||
definition_xml = etree.parse(file).getroot()
|
||||
definition_xml = cls.file_to_xml(file)
|
||||
except:
|
||||
log.exception("Failed to parse xml in file %s" % filepath)
|
||||
raise
|
||||
@@ -147,8 +158,8 @@ class XmlDescriptor(XModuleDescriptor):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _format_filepath(cls, type, name):
|
||||
return '{type}/{name}.{ext}'.format(type=type, name=name, ext=cls.filename_extension)
|
||||
def _format_filepath(cls, category, name):
|
||||
return u'{category}/{name}.{ext}'.format(category=category, name=name, ext=cls.filename_extension)
|
||||
|
||||
def export_to_xml(self, resource_fs):
|
||||
"""
|
||||
|
||||
@@ -8,5 +8,25 @@ Testing is good. Here is some useful info about how we set up tests--
|
||||
|
||||
### Frontend code:
|
||||
|
||||
- TODO
|
||||
We're using Jasmine to unit-testing the JavaScript files. All the specs are
|
||||
written in CoffeeScript for the consistency. To access the test cases, start the
|
||||
server in debug mode, navigate to `http://127.0.0.1:[port number]/_jasmine` to
|
||||
see the test result.
|
||||
|
||||
All the JavaScript codes must have test coverage. Both CMS and LMS
|
||||
has its own test directory in `{cms,lms}/static/coffee/spec` If you haven't
|
||||
written a JavaScript test before, you can look at those example files as a
|
||||
starting point. Also, these materials might be helpful for you:
|
||||
|
||||
CMS Note: For consistency, you're advised to use the same directory structure
|
||||
for implementation and test. For example, test for `src/views/module.coffee`
|
||||
should be written in `spec/views/module_spec.coffee`.
|
||||
|
||||
* http://pivotal.github.com/jasmine
|
||||
* http://railscasts.com/episodes/261-testing-javascript-with-jasmine?view=asciicast
|
||||
* http://a-developer-life.blogspot.com/2011/05/jasmine-part-1-unit-testing-javascript.html
|
||||
|
||||
If you're finishing a feature that contains JavaScript code snippets and do not
|
||||
sure how to test, please feel free to open up a pull request and asking people
|
||||
for help. (However, the best way to do it would be writing your test first, then
|
||||
implement your feature - Test Driven Development.)
|
||||
|
||||
@@ -71,6 +71,7 @@ class Settings(object):
|
||||
# Load the course settings as a dictionary
|
||||
course_settings = {}
|
||||
try:
|
||||
# TODO: this doesn't work with multicourse
|
||||
with open( settings.DATA_DIR + "/course_settings.json") as course_settings_file:
|
||||
course_settings_string = course_settings_file.read()
|
||||
course_settings = json.loads(course_settings_string)
|
||||
|
||||
@@ -274,11 +274,24 @@ def add_histogram(module):
|
||||
histogram = grade_histogram(module_id)
|
||||
render_histogram = len(histogram) > 0
|
||||
|
||||
# TODO: fixme - no filename in module.xml in general (this code block for edx4edx)
|
||||
# the following if block is for summer 2012 edX course development; it will change when the CMS comes online
|
||||
if settings.MITX_FEATURES.get('DISPLAY_EDIT_LINK') and settings.DEBUG and module_xml.get('filename') is not None:
|
||||
coursename = multicourse_settings.get_coursename_from_request(request)
|
||||
github_url = multicourse_settings.get_course_github_url(coursename)
|
||||
fn = module_xml.get('filename')
|
||||
if module_xml.tag=='problem': fn = 'problems/' + fn # grrr
|
||||
edit_link = (github_url + '/tree/master/' + fn) if github_url is not None else None
|
||||
if module_xml.tag=='problem': edit_link += '.xml' # grrr
|
||||
else:
|
||||
edit_link = False
|
||||
|
||||
# Cast module.definition and module.metadata to dicts so that json can dump them
|
||||
# even though they are lazily loaded
|
||||
staff_context = {'definition': json.dumps(dict(module.definition), indent=4),
|
||||
'metadata': json.dumps(dict(module.metadata), indent=4),
|
||||
'element_id': module.location.html_id(),
|
||||
'edit_link': edit_link,
|
||||
'histogram': json.dumps(histogram),
|
||||
'render_histogram': render_histogram,
|
||||
'module_content': original_get_html()}
|
||||
@@ -287,7 +300,6 @@ def add_histogram(module):
|
||||
module.get_html = get_html
|
||||
return module
|
||||
|
||||
|
||||
def modx_dispatch(request, dispatch=None, id=None):
|
||||
''' Generic view for extensions. This is where AJAX calls go.
|
||||
|
||||
|
||||
@@ -117,6 +117,9 @@ def render_accordion(request, course, chapter, section):
|
||||
|
||||
Returns (initialization_javascript, content)'''
|
||||
|
||||
if not course:
|
||||
course = settings.COURSE_DEFAULT.replace('_',' ')
|
||||
|
||||
course_location = multicourse_settings.get_course_location(course)
|
||||
toc = toc_for_course(request.user, request, course_location, chapter, section)
|
||||
|
||||
@@ -142,9 +145,10 @@ def get_course(request, course):
|
||||
|
||||
if course == None:
|
||||
if not settings.ENABLE_MULTICOURSE:
|
||||
course = "6.002 Spring 2012"
|
||||
course = settings.COURSE_DEFAULT
|
||||
elif 'coursename' in request.session:
|
||||
course = request.session['coursename']
|
||||
# use multicourse_settings, so that settings.COURSE_TITLE is set properly
|
||||
course = multicourse_settings.get_coursename_from_request(request)
|
||||
else:
|
||||
course = settings.COURSE_DEFAULT
|
||||
return course
|
||||
@@ -183,17 +187,34 @@ def index(request, course=None, chapter=None, section=None,
|
||||
|
||||
course = clean(get_course(request, course))
|
||||
if not multicourse_settings.is_valid_course(course):
|
||||
log.debug('Course %s is not valid' % course)
|
||||
return redirect('/')
|
||||
|
||||
# keep track of current course being viewed in django's request.session
|
||||
request.session['coursename'] = course
|
||||
|
||||
# get default chapter & section from multicourse settings, if not provided
|
||||
if chapter is None:
|
||||
defchapter = multicourse_settings.get_course_default_chapter(course)
|
||||
defsection = multicourse_settings.get_course_default_section(course)
|
||||
if defchapter and defsection:
|
||||
# jump there using redirect, so the user gets the right URL in their browser
|
||||
newurl = '%s/courseware/%s/%s/%s/' % (settings.MITX_ROOT_URL,
|
||||
get_course(request, course).replace(' ','_'),
|
||||
defchapter,
|
||||
defsection)
|
||||
log.debug('redirecting to %s' % newurl)
|
||||
return redirect(newurl)
|
||||
|
||||
chapter = clean(chapter)
|
||||
section = clean(section)
|
||||
|
||||
if settings.ENABLE_MULTICOURSE:
|
||||
settings.MODULESTORE['default']['OPTIONS']['data_dir'] = settings.DATA_DIR + multicourse_settings.get_course_xmlpath(course)
|
||||
|
||||
context = {
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'accordion': render_accordion(request, course, chapter, section),
|
||||
'accordion': render_accordion(request, course, chapter, section), # triggers course load!
|
||||
'COURSE_TITLE': multicourse_settings.get_course_title(course),
|
||||
'init': '',
|
||||
'content': ''
|
||||
|
||||
@@ -94,6 +94,14 @@ def get_course_title(coursename):
|
||||
def get_course_number(coursename):
|
||||
return get_course_property(coursename, 'number')
|
||||
|
||||
def get_course_github_url(coursename):
|
||||
return get_course_property(coursename,'github_url')
|
||||
|
||||
def get_course_default_chapter(coursename):
|
||||
return get_course_property(coursename,'default_chapter')
|
||||
|
||||
def get_course_default_section(coursename):
|
||||
return get_course_property(coursename,'default_section')
|
||||
|
||||
def get_course_location(coursename):
|
||||
return get_course_property(coursename, 'location')
|
||||
|
||||
14
lms/djangoapps/student/admin.py
Normal file
@@ -0,0 +1,14 @@
|
||||
'''
|
||||
django admin pages for courseware model
|
||||
'''
|
||||
|
||||
from student.models import *
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
admin.site.register(UserProfile)
|
||||
|
||||
admin.site.register(Registration)
|
||||
|
||||
admin.site.register(PendingNameChange)
|
||||
|
||||
@@ -191,7 +191,9 @@ def create_account(request, post_override=None):
|
||||
up.save()
|
||||
|
||||
d={'name':post_vars['name'],
|
||||
'key':r.activation_key}
|
||||
'key':r.activation_key,
|
||||
'course_title' : settings.COURSE_TITLE,
|
||||
}
|
||||
|
||||
subject = render_to_string('emails/activation_email_subject.txt',d)
|
||||
# Email subject *must not* contain newlines
|
||||
@@ -199,7 +201,11 @@ def create_account(request, post_override=None):
|
||||
message = render_to_string('emails/activation_email.txt',d)
|
||||
|
||||
try:
|
||||
if not settings.GENERATE_RANDOM_USER_CREDENTIALS:
|
||||
if settings.MITX_FEATURES.get('REROUTE_ACTIVATION_EMAIL'):
|
||||
dest_addr = settings.MITX_FEATURES['REROUTE_ACTIVATION_EMAIL']
|
||||
message = "Activation for %s (%s): %s\n" % (u,u.email,up.name) + '-'*80 + '\n\n' + message
|
||||
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [dest_addr], fail_silently=False)
|
||||
elif not settings.GENERATE_RANDOM_USER_CREDENTIALS:
|
||||
res=u.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
|
||||
except:
|
||||
log.exception(sys.exc_info())
|
||||
|
||||
@@ -38,6 +38,8 @@ MITX_FEATURES = {
|
||||
'SAMPLE' : False,
|
||||
'USE_DJANGO_PIPELINE' : True,
|
||||
'DISPLAY_HISTOGRAMS_TO_STAFF' : True,
|
||||
'REROUTE_ACTIVATION_EMAIL' : False, # nonempty string = address for all activation emails
|
||||
'DEBUG_LEVEL' : 0, # 0 = lowest level, least verbose, 255 = max level, most verbose
|
||||
}
|
||||
|
||||
# Used for A/B testing
|
||||
@@ -128,11 +130,11 @@ QUICKEDIT = False
|
||||
|
||||
###
|
||||
|
||||
COURSE_DEFAULT = '6.002_Spring_2012'
|
||||
COURSE_SETTINGS = {'6.002_Spring_2012': {'number' : '6.002x',
|
||||
COURSE_DEFAULT = '6.002x_Fall_2012'
|
||||
COURSE_SETTINGS = {'6.002x_Fall_2012': {'number' : '6.002x',
|
||||
'title' : 'Circuits and Electronics',
|
||||
'xmlpath': '6002x/',
|
||||
'location': 'i4x://edx/6002xs12/course/6.002_Spring_2012',
|
||||
'location': 'i4x://edx/6002xs12/course/6.002x_Fall_2012',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,8 +52,8 @@ CACHES = {
|
||||
SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
|
||||
|
||||
################################ DEBUG TOOLBAR #################################
|
||||
INSTALLED_APPS += ('debug_toolbar',)
|
||||
MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
|
||||
#INSTALLED_APPS += ('debug_toolbar',)
|
||||
#MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
|
||||
INTERNAL_IPS = ('127.0.0.1',)
|
||||
|
||||
DEBUG_TOOLBAR_PANELS = (
|
||||
|
||||
77
lms/envs/dev_edx4edx.py
Normal file
@@ -0,0 +1,77 @@
|
||||
"""
|
||||
This config file runs the simplest dev environment using sqlite, and db-based
|
||||
sessions. Assumes structure:
|
||||
|
||||
/envroot/
|
||||
/db # This is where it'll write the database file
|
||||
/mitx # The location of this repo
|
||||
/log # Where we're going to write log files
|
||||
"""
|
||||
|
||||
import socket
|
||||
|
||||
if 'eecs1' in socket.gethostname():
|
||||
MITX_ROOT_URL = '/mitx2'
|
||||
|
||||
from .common import *
|
||||
from .logsettings import get_logger_config
|
||||
from .dev import *
|
||||
|
||||
if 'eecs1' in socket.gethostname():
|
||||
MITX_ROOT_URL = '/mitx2'
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# edx4edx content server
|
||||
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
MITX_FEATURES['REROUTE_ACTIVATION_EMAIL'] = 'ichuang@mitx.mit.edu'
|
||||
EDX4EDX_ROOT = ENV_ROOT / "data/edx4edx"
|
||||
|
||||
#EMAIL_BACKEND = 'django_ses.SESBackend'
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# ichuang
|
||||
|
||||
DEBUG = True
|
||||
ENABLE_MULTICOURSE = True # set to False to disable multicourse display (see lib.util.views.mitxhome)
|
||||
QUICKEDIT = True
|
||||
|
||||
MAKO_TEMPLATES['course'] = [DATA_DIR, EDX4EDX_ROOT ]
|
||||
|
||||
#MITX_FEATURES['USE_DJANGO_PIPELINE'] = False
|
||||
MITX_FEATURES['DISPLAY_HISTOGRAMS_TO_STAFF'] = False
|
||||
MITX_FEATURES['DISPLAY_EDIT_LINK'] = True
|
||||
|
||||
COURSE_DEFAULT = "edx4edx"
|
||||
COURSE_NAME = "edx4edx"
|
||||
COURSE_NUMBER = "edX.01"
|
||||
COURSE_TITLE = "edx4edx: edX Author Course"
|
||||
SITE_NAME = "ichuang.mitx.mit.edu"
|
||||
|
||||
COURSE_SETTINGS = {'edx4edx': {'number' : 'edX.01',
|
||||
'title' : 'edx4edx: edX Author Course',
|
||||
'xmlpath': '/edx4edx/',
|
||||
'github_url': 'https://github.com/MITx/edx4edx',
|
||||
'active' : True,
|
||||
'default_chapter' : 'Introduction',
|
||||
'default_section' : 'edx4edx_Course',
|
||||
},
|
||||
}
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + (
|
||||
'ssl_auth.ssl_auth.NginxProxyHeaderMiddleware', # ssl authentication behind nginx proxy
|
||||
)
|
||||
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
'ssl_auth.ssl_auth.SSLLoginBackend',
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
)
|
||||
|
||||
INSTALLED_APPS = INSTALLED_APPS + (
|
||||
'ssl_auth',
|
||||
)
|
||||
|
||||
LOGIN_REDIRECT_URL = MITX_ROOT_URL + '/'
|
||||
LOGIN_URL = MITX_ROOT_URL + '/'
|
||||
@@ -18,47 +18,114 @@ from .logsettings import get_logger_config
|
||||
from .dev import *
|
||||
|
||||
if 'eecs1' in socket.gethostname():
|
||||
MITX_ROOT_URL = '/mitx2'
|
||||
# MITX_ROOT_URL = '/mitx2'
|
||||
MITX_ROOT_URL = 'https://eecs1.mit.edu/mitx2'
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# edx4edx content server
|
||||
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
MITX_FEATURES['REROUTE_ACTIVATION_EMAIL'] = 'ichuang@mit.edu'
|
||||
EDX4EDX_ROOT = ENV_ROOT / "data/edx4edx"
|
||||
|
||||
#EMAIL_BACKEND = 'django_ses.SESBackend'
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# ichuang
|
||||
|
||||
DEBUG = True
|
||||
ENABLE_MULTICOURSE = True # set to False to disable multicourse display (see lib.util.views.mitxhome)
|
||||
QUICKEDIT = True
|
||||
QUICKEDIT = False
|
||||
|
||||
# MITX_FEATURES['USE_DJANGO_PIPELINE'] = False
|
||||
MAKO_TEMPLATES['course'] = [DATA_DIR, EDX4EDX_ROOT ]
|
||||
|
||||
#MITX_FEATURES['USE_DJANGO_PIPELINE'] = False
|
||||
MITX_FEATURES['DISPLAY_HISTOGRAMS_TO_STAFF'] = False
|
||||
MITX_FEATURES['DISPLAY_EDIT_LINK'] = True
|
||||
MITX_FEATURES['DEBUG_LEVEL'] = 10 # 0 = lowest level, least verbose, 255 = max level, most verbose
|
||||
|
||||
COURSE_SETTINGS = {'6.002_Spring_2012': {'number' : '6.002x',
|
||||
COURSE_SETTINGS = {'6.002x_Fall_2012': {'number' : '6.002x',
|
||||
'title' : 'Circuits and Electronics',
|
||||
'xmlpath': '/6002x/',
|
||||
'xmlpath': '/6002x-fall-2012/',
|
||||
'active' : True,
|
||||
'default_chapter' : 'Week_1',
|
||||
'default_section' : 'Administrivia_and_Circuit_Elements',
|
||||
'location': 'i4x://edx/6002xs12/course/6.002x_Fall_2012',
|
||||
},
|
||||
'8.02_Spring_2013': {'number' : '8.02x',
|
||||
'title' : 'Electricity & Magnetism',
|
||||
'xmlpath': '/802x/',
|
||||
'active' : True,
|
||||
},
|
||||
'8.01_Spring_2013': {'number' : '8.01x',
|
||||
'title' : 'Mechanics',
|
||||
'xmlpath': '/801x/',
|
||||
'active' : False,
|
||||
'github_url': 'https://github.com/MITx/8.02x',
|
||||
'active' : True,
|
||||
'default_chapter' : 'Introduction',
|
||||
'default_section' : 'Introduction_%28Lewin_2002%29',
|
||||
},
|
||||
'6.189_Spring_2013': {'number' : '6.189x',
|
||||
'title' : 'IAP Python Programming',
|
||||
'xmlpath': '/6189-pytutor/',
|
||||
'active' : True,
|
||||
'xmlpath': '/6.189x/',
|
||||
'github_url': 'https://github.com/MITx/6.189x',
|
||||
'active' : True,
|
||||
'default_chapter' : 'Week_1',
|
||||
'default_section' : 'Variables_and_Binding',
|
||||
},
|
||||
'8.01_Summer_2012': {'number' : '8.01x',
|
||||
'8.01_Fall_2012': {'number' : '8.01x',
|
||||
'title' : 'Mechanics',
|
||||
'xmlpath': '/801x-summer/',
|
||||
'xmlpath': '/8.01x/',
|
||||
'github_url': 'https://github.com/MITx/8.01x',
|
||||
'active': True,
|
||||
'default_chapter' : 'Mechanics_Online_Spring_2012',
|
||||
'default_section' : 'Introduction_to_the_course',
|
||||
'location': 'i4x://edx/6002xs12/course/8.01_Fall_2012',
|
||||
},
|
||||
'edx4edx': {'number' : 'edX.01',
|
||||
'title' : 'edx4edx: edX Author Course',
|
||||
'xmlpath': '/edx4edx/',
|
||||
'github_url': 'https://github.com/MITx/edx4edx',
|
||||
'active' : True,
|
||||
'default_chapter' : 'Introduction',
|
||||
'default_section' : 'edx4edx_Course',
|
||||
'location': 'i4x://edx/6002xs12/course/edx4edx',
|
||||
},
|
||||
'7.03x_Fall_2012': {'number' : '7.03x',
|
||||
'title' : 'Genetics',
|
||||
'xmlpath': '/7.03x/',
|
||||
'github_url': 'https://github.com/MITx/7.03x',
|
||||
'active' : True,
|
||||
'default_chapter' : 'Week_2',
|
||||
'default_section' : 'ps1_question_1',
|
||||
},
|
||||
'3.091x_Fall_2012': {'number' : '3.091x',
|
||||
'title' : 'Introduction to Solid State Chemistry',
|
||||
'xmlpath': '/3.091x/',
|
||||
'github_url': 'https://github.com/MITx/3.091x',
|
||||
'active' : True,
|
||||
'default_chapter' : 'Week_1',
|
||||
'default_section' : 'Problem_Set_1',
|
||||
},
|
||||
'18.06x_Linear_Algebra': {'number' : '18.06x',
|
||||
'title' : 'Linear Algebra',
|
||||
'xmlpath': '/18.06x/',
|
||||
'github_url': 'https://github.com/MITx/18.06x',
|
||||
'default_chapter' : 'Unit_1',
|
||||
'default_section' : 'Midterm_1',
|
||||
'active' : True,
|
||||
},
|
||||
'6.00x_Fall_2012': {'number' : '6.00x',
|
||||
'title' : 'Introduction to Computer Science and Programming',
|
||||
'xmlpath': '/6.00x/',
|
||||
'github_url': 'https://github.com/MITx/6.00x',
|
||||
'active' : True,
|
||||
'default_chapter' : 'Week_0',
|
||||
'default_section' : 'Problem_Set_0',
|
||||
'location': 'i4x://edx/6002xs12/course/6.00x_Fall_2012',
|
||||
},
|
||||
'7.00x_Fall_2012': {'number' : '7.00x',
|
||||
'title' : 'Introduction to Biology',
|
||||
'xmlpath': '/7.00x/',
|
||||
'github_url': 'https://github.com/MITx/7.00x',
|
||||
'active' : True,
|
||||
'default_chapter' : 'Unit 1',
|
||||
'default_section' : 'Introduction',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
38
lms/envs/edx4edx_aws.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# Settings for edx4edx production instance
|
||||
from .aws import *
|
||||
COURSE_NAME = "edx4edx"
|
||||
COURSE_NUMBER = "edX.01"
|
||||
COURSE_TITLE = "edx4edx: edX Author Course"
|
||||
EDX4EDX_ROOT = ENV_ROOT / "data/edx4edx"
|
||||
|
||||
### Dark code. Should be enabled in local settings for devel.
|
||||
QUICKEDIT = True
|
||||
ENABLE_MULTICOURSE = True # set to False to disable multicourse display (see lib.util.views.mitxhome)
|
||||
###
|
||||
PIPELINE_CSS_COMPRESSOR = None
|
||||
PIPELINE_JS_COMPRESSOR = None
|
||||
|
||||
COURSE_DEFAULT = 'edx4edx'
|
||||
COURSE_SETTINGS = {'edx4edx': {'number' : 'edX.01',
|
||||
'title' : 'edx4edx: edX Author Course',
|
||||
'xmlpath': '/edx4edx/',
|
||||
'github_url': 'https://github.com/MITx/edx4edx',
|
||||
'active' : True,
|
||||
'default_chapter' : 'Introduction',
|
||||
'default_section' : 'edx4edx_Course',
|
||||
},
|
||||
}
|
||||
|
||||
STATICFILES_DIRS = [
|
||||
PROJECT_ROOT / "static",
|
||||
ASKBOT_ROOT / "askbot" / "skins",
|
||||
("edx4edx", EDX4EDX_ROOT / "html"),
|
||||
("circuits", DATA_DIR / "images"),
|
||||
("handouts", DATA_DIR / "handouts"),
|
||||
("subs", DATA_DIR / "subs"),
|
||||
|
||||
# This is how you would use the textbook images locally
|
||||
# ("book", ENV_ROOT / "book_images")
|
||||
]
|
||||
|
||||
MAKO_TEMPLATES['course'] = [DATA_DIR, EDX4EDX_ROOT ]
|
||||
@@ -20,7 +20,8 @@ INSTALLED_APPS = [
|
||||
|
||||
# Nose Test Runner
|
||||
INSTALLED_APPS += ['django_nose']
|
||||
NOSE_ARGS = ['--cover-erase', '--with-xunit', '--with-xcoverage', '--cover-html', '--cover-inclusive', '--cover-html-dir', os.environ['NOSE_COVER_HTML_DIR']]
|
||||
NOSE_ARGS = ['--cover-erase', '--with-xunit', '--with-xcoverage', '--cover-html',
|
||||
'--cover-inclusive', '--cover-html-dir', os.environ.get('NOSE_COVER_HTML_DIR', 'cover_html')]
|
||||
for app in os.listdir(PROJECT_ROOT / 'djangoapps'):
|
||||
NOSE_ARGS += ['--cover-package', app]
|
||||
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
|
||||
|
||||
@@ -24,7 +24,7 @@ def check_problem_code(ans,the_lcp,correct_answers,false_answers):
|
||||
pfn += the_lcp.problem_id.replace('filename','') # add problem ID to dogfood problem name
|
||||
update_problem(pfn,ans,filestore=the_lcp.system.filestore)
|
||||
msg = '<hr width="100%"/>'
|
||||
msg += '<iframe src="%s/dogfood/filename%s" width="95%%" frameborder="1">No iframe support!</iframe>' % (settings.MITX_ROOT_URL,pfn)
|
||||
msg += '<iframe src="%s/dogfood/filename%s" width="95%%" height="400" frameborder="1">No iframe support!</iframe>' % (settings.MITX_ROOT_URL,pfn)
|
||||
msg += '<hr width="100%"/>'
|
||||
|
||||
endmsg = """<p><font size="-1" color="purple">Note: if the code text box disappears after clicking on "Check",
|
||||
|
||||
@@ -25,7 +25,7 @@ import track.views
|
||||
from lxml import etree
|
||||
|
||||
|
||||
from courseware.module_render import make_track_function, I4xSystem
|
||||
from courseware.module_render import make_track_function, I4xSystem, get_module
|
||||
from courseware.models import StudentModule
|
||||
from multicourse import multicourse_settings
|
||||
from student.models import UserProfile
|
||||
@@ -55,6 +55,7 @@ def update_problem(pfn,pxml,coursename=None,overwrite=True,filestore=None):
|
||||
else:
|
||||
pfn2 = 'problems/%s.xml' % pfn
|
||||
fp = filestore.open(pfn2,'w')
|
||||
log.debug('[dogfood.update_problem] pfn2=%s' % pfn2)
|
||||
|
||||
if os.path.exists(pfn2) and not overwrite: return # don't overwrite if already exists and overwrite=False
|
||||
pxmls = pxml if type(pxml) in [str,unicode] else etree.tostring(pxml,pretty_print=True)
|
||||
@@ -71,7 +72,7 @@ def df_capa_problem(request, id=None):
|
||||
# "WARNING: UNDEPLOYABLE CODE. FOR DEV USE ONLY."
|
||||
|
||||
if settings.DEBUG:
|
||||
print '[lib.dogfood.df_capa_problem] id=%s' % id
|
||||
log.debug('[lib.dogfood.df_capa_problem] id=%s' % id)
|
||||
|
||||
if not 'coursename' in request.session:
|
||||
coursename = DOGFOOD_COURSENAME
|
||||
@@ -86,7 +87,7 @@ def df_capa_problem(request, id=None):
|
||||
try:
|
||||
xml = content_parser.module_xml(request.user, module, 'id', id, coursename)
|
||||
except Exception,err:
|
||||
print "[lib.dogfood.df_capa_problem] error in calling content_parser: %s" % err
|
||||
log.error("[lib.dogfood.df_capa_problem] error in calling content_parser: %s" % err)
|
||||
xml = None
|
||||
|
||||
# if problem of given ID does not exist, then create it
|
||||
@@ -96,7 +97,7 @@ def df_capa_problem(request, id=None):
|
||||
if not m:
|
||||
raise Exception,'[lib.dogfood.df_capa_problem] Illegal problem id %s' % id
|
||||
pfn = m.group(1)
|
||||
print '[lib.dogfood.df_capa_problem] creating new problem pfn=%s' % pfn
|
||||
log.debug('[lib.dogfood.df_capa_problem] creating new problem pfn=%s' % pfn)
|
||||
|
||||
# add problem to course.xml
|
||||
fn = settings.DATA_DIR + xp + 'course.xml'
|
||||
@@ -126,7 +127,7 @@ def df_capa_problem(request, id=None):
|
||||
'groups' : groups}
|
||||
filename = xp + 'course.xml'
|
||||
cache_key = filename + "_processed?dev_content:" + str(options['dev_content']) + "&groups:" + str(sorted(groups))
|
||||
print '[lib.dogfood.df_capa_problem] cache_key = %s' % cache_key
|
||||
log.debug('[lib.dogfood.df_capa_problem] cache_key = %s' % cache_key)
|
||||
#cache.delete(cache_key)
|
||||
tree = content_parser.course_xml_process(xml) # add ID tags
|
||||
cache.set(cache_key,etree.tostring(tree),60)
|
||||
@@ -134,7 +135,7 @@ def df_capa_problem(request, id=None):
|
||||
|
||||
xml = content_parser.module_xml(request.user, module, 'id', id, coursename)
|
||||
if not xml:
|
||||
print "[lib.dogfood.df_capa_problem] problem xml not found!"
|
||||
log.debug("[lib.dogfood.df_capa_problem] problem xml not found!")
|
||||
|
||||
# add problem ID to list so that is_staff check can be bypassed
|
||||
request.session['dogfood_id'] = id
|
||||
@@ -170,6 +171,31 @@ def quickedit(request, id=None, qetemplate='quickedit.html',coursename=None):
|
||||
xp = multicourse_settings.get_course_xmlpath(coursename) # path to XML for the course
|
||||
|
||||
def get_lcp(coursename,id):
|
||||
# Grab the XML corresponding to the request from course.xml
|
||||
# create empty student state for this problem, if not previously existing
|
||||
s = StudentModule.objects.filter(student=request.user,
|
||||
module_id=id)
|
||||
student_module_cache = list(s) if s is not None else []
|
||||
#if len(s) == 0 or s is None:
|
||||
# smod=StudentModule(student=request.user,
|
||||
# module_type = 'problem',
|
||||
# module_id=id,
|
||||
# state=instance.get_state())
|
||||
# smod.save()
|
||||
# student_module_cache = [smod]
|
||||
module = 'problem'
|
||||
module_xml = etree.XML(content_parser.module_xml(request.user, module, 'id', id, coursename))
|
||||
module_id = module_xml.get('id')
|
||||
log.debug("module_id = %s" % module_id)
|
||||
(instance,smod,module_type) = get_module(request.user, request, module_xml, student_module_cache, position=None)
|
||||
log.debug('[dogfood.views] instance=%s' % instance)
|
||||
lcp = instance.lcp
|
||||
log.debug('[dogfood.views] lcp=%s' % lcp)
|
||||
pxml = lcp.tree
|
||||
pxmls = etree.tostring(pxml,pretty_print=True)
|
||||
return instance, pxmls
|
||||
|
||||
def old_get_lcp(coursename,id):
|
||||
# Grab the XML corresponding to the request from course.xml
|
||||
module = 'problem'
|
||||
xml = content_parser.module_xml(request.user, module, 'id', id, coursename)
|
||||
@@ -280,7 +306,8 @@ def quickedit_git_reload(request):
|
||||
|
||||
if 'gitupdate' in request.POST:
|
||||
import os # FIXME - put at top?
|
||||
cmd = "cd ../data%s; git reset --hard HEAD; git pull origin %s" % (xp,xp.replace('/',''))
|
||||
#cmd = "cd ../data%s; git reset --hard HEAD; git pull origin %s" % (xp,xp.replace('/',''))
|
||||
cmd = "cd ../data%s; ./GITRELOAD '%s'" % (xp,xp.replace('/',''))
|
||||
msg += '<p>cmd: %s</p>' % cmd
|
||||
ret = os.popen(cmd).read()
|
||||
msg += '<p><pre>%s</pre></p>' % ret.replace('<','<')
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
# Python functions which duplicate the standard comparison functions available to LON-CAPA problems.
|
||||
# Used in translating LON-CAPA problems to i4x problem specification language.
|
||||
|
||||
from __future__ import division
|
||||
import random
|
||||
import math
|
||||
|
||||
def lc_random(lower,upper,stepsize):
|
||||
'''
|
||||
@@ -15,3 +17,20 @@ def lc_random(lower,upper,stepsize):
|
||||
choices = [lower+x*stepsize for x in range(nstep)]
|
||||
return random.choice(choices)
|
||||
|
||||
def lc_choose(index,*args):
|
||||
'''
|
||||
return args[index]
|
||||
'''
|
||||
try:
|
||||
return args[int(index)-1]
|
||||
except Exception,err:
|
||||
pass
|
||||
if len(args):
|
||||
return args[0]
|
||||
raise Exception,"loncapa_check.lc_choose error, index=%s, args=%s" % (index,args)
|
||||
|
||||
deg2rad = math.pi/180.0
|
||||
rad2deg = 180.0/math.pi
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -231,6 +231,9 @@ def symmath_check(expect,ans,dynamath=None,options=None,debug=None):
|
||||
dm = my_evalf(sympy.Matrix(fexpect)-sympy.Matrix(xgiven),chop=True)
|
||||
if abs(dm.vec().norm().evalf())<threshold:
|
||||
return {'ok': True,'msg': msg}
|
||||
except sympy.ShapeError:
|
||||
msg += "<p>Error - your input vector or matrix has the wrong dimensions"
|
||||
return {'ok':False,'msg':msg}
|
||||
except Exception,err:
|
||||
msg += "<p>Error %s in comparing expected (a list) and your answer</p>" % str(err).replace('<','<')
|
||||
if DEBUG: msg += "<p/><pre>%s</pre>" % traceback.format_exc()
|
||||
|
||||
@@ -205,17 +205,17 @@ section.problem-set {
|
||||
table {
|
||||
margin-bottom: lh();
|
||||
width: 100%;
|
||||
border: 1px solid #ddd;
|
||||
// border: 1px solid #ddd;
|
||||
border-collapse: collapse;
|
||||
|
||||
th {
|
||||
border-bottom: 2px solid #ccc;
|
||||
// border-bottom: 2px solid #ccc;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
td {
|
||||
border: 1px solid #ddd;
|
||||
// border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
caption, th, td {
|
||||
@@ -253,11 +253,12 @@ section.problem-set {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
display: inline-block;
|
||||
width: 50%;
|
||||
#{$all-text-inputs} {
|
||||
display: inline;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
// this supports a deprecated element and should be removed once the center tag is removed
|
||||
center {
|
||||
display: block;
|
||||
margin: lh() 0;
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
% endfor
|
||||
|
||||
## add a link to allow course.xml to be reloaded from the git content repo
|
||||
% if settings.QUICKEDIT:
|
||||
<h3><a href="#">quickedit</a></h3>
|
||||
<ul><li><a href="${MITX_ROOT_URL}/quickedit/course.xml">gitreload</a></li></ul>
|
||||
% endif
|
||||
##% if settings.QUICKEDIT:
|
||||
## <h3><a href="#">quickedit</a></h3>
|
||||
## <ul><li><a href="${MITX_ROOT_URL}/quickedit/course.xml">gitreload</a></li></ul>
|
||||
##% endif
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<%inherit file="main.html" />
|
||||
<%block name="bodyclass">courseware</%block>
|
||||
<%block name="title"><title>Courseware – MITx 6.002x</title></%block>
|
||||
<%block name="title"><title>Courseware – edX</title></%block>
|
||||
|
||||
<%include file="navigation.html" args="active_page='courseware'" />
|
||||
|
||||
<section class="main-content">
|
||||
<section class="outside-app">
|
||||
<h1>There has been an error on the <em>MITx</em> servers</h1>
|
||||
<h1>There has been an error on the <em>edX</em> servers</h1>
|
||||
<p>We're sorry, this module is temporarily unavailable. Our staff is working to fix it as soon as possible. Please email us at <a href="mailto:technical@mitx.mit.edu">technical@mitx.mit.edu</a> to report any problems or downtime.</p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<!-- TODO: Add pattern field to username. See HTML5 cookbook, page 84 for details-->
|
||||
<div name="enroll_form" id="enroll_form">
|
||||
|
||||
<h1>Enroll in 6.002x Circuits & Electronics</h1>
|
||||
<h1>Enroll in edx4edx</h1>
|
||||
<!--[if lte IE 8]>
|
||||
<p class="ie-warning"> Enrollment requires a modern web browser with JavaScript enabled. You don't have this. You can’t enroll without upgrading, since you couldn’t take the course without upgrading. Feel free to download the latest version of <a href="http://www.mozilla.org/en-US/firefox/new/">Mozilla Firefox</a> or <a href="http://support.google.com/chrome/bin/answer.py?hl=en&answer=95346">Google Chrome</a>, for free, to enroll and take this course.</p>
|
||||
<![endif]-->
|
||||
<p class="disclaimer">
|
||||
Please note that 6.002x has now passed its half-way point. The midterm exam and several assignment due dates for 6.002x have already passed. It is now impossible for newly enrolled students to earn a passing grade and a completion certificate for the course. However, new students have access to all of the course material that has been released for the course, so you are welcome to enroll and browse the course. </p>
|
||||
</p>
|
||||
<form name="enroll" id="enroll_form" method="get">
|
||||
<fieldset><% if 'error' in locals(): e = error %>
|
||||
|
||||
@@ -35,7 +35,7 @@ Please note that 6.002x has now passed its half-way point. The midterm exam and
|
||||
<label>Full name*<span class="ui-icon ui-icon-help" id="spinner_name" style="display:inline-block;"></span></label>
|
||||
<input name="name" id="ca_name" type="text" />
|
||||
|
||||
<div class="tip" id="sregion_name">If you successfully complete the course, you will receive an electronic certificate of accomplishment from <i>MITx</i> with this name on it.</div>
|
||||
<div class="tip" id="sregion_name">If you successfully complete the course, you will receive an electronic certificate of accomplishment from <i>edX</i> with this name on it.</div>
|
||||
</li>
|
||||
|
||||
<li class="location">
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
Someone, hopefully you, signed up for an account for MITx's on-line
|
||||
offering of 6.002 using this email address. If it was you, and you'd
|
||||
like to activate and use your account, copy and paste this address
|
||||
into your web browser's address bar:
|
||||
Someone, hopefully you, signed up for an account for edX's on-line
|
||||
offering of "${ course_title}" using this email address. If it was
|
||||
you, and you'd like to activate and use your account, copy and paste
|
||||
this address into your web browser's address bar:
|
||||
|
||||
% if is_secure:
|
||||
https://${ site }/activate/${ key }
|
||||
% else:
|
||||
http://${ site }/activate/${ key }
|
||||
http://edx4edx.mitx.mit.edu/activate/${ key }
|
||||
% endif
|
||||
|
||||
If you didn't request this, you don't need to do anything; you won't
|
||||
receive any more email from us. Please do not reply to this e-mail; if
|
||||
you require assistance, check the help section of the MITx web site.
|
||||
you require assistance, check the help section of the edX web site.
|
||||
|
||||
@@ -1 +1 @@
|
||||
Your account for MITx's on-line 6.002
|
||||
Your account for edX's on-line ${course_title} course
|
||||
|
||||
@@ -4,24 +4,24 @@
|
||||
<div>
|
||||
<h1> Collaboration Policy </h1>
|
||||
|
||||
<p> By enrolling in a course on <i>MITx</i>, you are joining a
|
||||
<p> By enrolling in a course on <i>edX</i>, you are joining a
|
||||
special worldwide community of learners. The aspiration
|
||||
of <i>MITx</i> is to provide anyone in the world who has the
|
||||
motivation and ability to engage MIT coursework the opportunity
|
||||
to attain the best MIT-based educational experience that
|
||||
of <i>edX</i> is to provide anyone in the world who has the
|
||||
motivation and ability to engage edX coursework the opportunity
|
||||
to attain the best edX-based educational experience that
|
||||
Internet technology enables. You are part of the community who
|
||||
will help <i>MITx</i> achieve this goal.
|
||||
will help <i>edX</i> achieve this goal.
|
||||
|
||||
<p> <i>MITx</i> depends upon your motivation to learn the material
|
||||
<p> <i>edX</i> depends upon your motivation to learn the material
|
||||
and to do so with honesty. In order to participate
|
||||
in <i>MITx</i>, you must agree to the Honor Code below and any
|
||||
in <i>edX</i>, you must agree to the Honor Code below and any
|
||||
additional terms specific to a class. This Honor Code, and any
|
||||
additional terms, will be posted on each class website.
|
||||
|
||||
<div style="color:darkred;">
|
||||
<h2> <i>MITx</i> Honor Code Pledge</h2>
|
||||
<h2> <i>edX</i> Honor Code Pledge</h2>
|
||||
|
||||
<p> By enrolling in an <i>MITx</i> course, I agree that I will:
|
||||
<p> By enrolling in an <i>edX</i> course, I agree that I will:
|
||||
|
||||
<ul>
|
||||
<li> Complete all mid-terms and final exams with my own work
|
||||
@@ -35,14 +35,14 @@
|
||||
assess student performance.
|
||||
</ul>
|
||||
</div>
|
||||
<p> Unless otherwise indicated by the instructor of an <i>MITx</i>
|
||||
course, learners on <i>MITx</i> are encouraged to:
|
||||
<p> Unless otherwise indicated by the instructor of an <i>edX</i>
|
||||
course, learners on <i>edX</i> are encouraged to:
|
||||
<ul>
|
||||
<li> Collaborate with others on the lecture videos, exercises,
|
||||
homework and labs.
|
||||
<li> Discuss with others general concepts and materials in
|
||||
each course.
|
||||
<li> Present ideas and written work to fellow <i>MITx</i>
|
||||
<li> Present ideas and written work to fellow <i>edX</i>
|
||||
learners or others for comment or criticism.
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -1,85 +1,25 @@
|
||||
<%inherit file="marketing.html" />
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
|
||||
<%block name="title">MITx 6.002x: Circuits & Electronics</%block>
|
||||
<%block name="description">6.002x (Circuits and Electronics) is an experimental on-line adaptation of MIT's first undergraduate analog design course: 6.002.</%block>
|
||||
<%block name="keywords">MITx, circuits, electronics, EECS, electrical engineering, analog circuits, digital circuits, online learning, MIT, online laboratory, education, learners, undergraduate, certificate</%block>
|
||||
<%block name="title">edx4edx author course</%block>
|
||||
<%block name="description">edx4edx</%block>
|
||||
<%block name="keywords">MITx, edx4edx</%block>
|
||||
|
||||
<%block name="header_text">
|
||||
<section class="course">
|
||||
<section>
|
||||
<h1>Circuits & Electronics</h1>
|
||||
<h2>6.002x</h2>
|
||||
<a class="enroll" rel="leanModal" href="/info">View 6.002x Circuits <span>&</span> Electronics as a guest</a>
|
||||
<a class="enroll" rel="leanModal" href="#enroll"><noscript>In order to</noscript> Enroll in 6.002x Circuits <span>&</span> Electronics <noscript>you need to have javascript enabled</noscript></a>
|
||||
<h1>edx4edx</h1>
|
||||
<h2>edX Author Course</h2>
|
||||
<a class="enroll" rel="leanModal" href="#enroll"><noscript>In
|
||||
order to</noscript> Enroll in edx4edx <noscript>you need to have javascript enabled</noscript></a>
|
||||
</section>
|
||||
<p>6.002x (Circuits and Electronics) is an experimental on-line adaptation of MIT’s first undergraduate analog design course: 6.002. This course is running, free of charge, for students worldwide from March 5, 2012 through June 8, 2012.</p>
|
||||
<p>edx4edx is a course for prospective edX course authors</p>
|
||||
</section>
|
||||
</%block>
|
||||
|
||||
<%block name="header_class">course</%block>
|
||||
|
||||
<section class="index-content">
|
||||
<section class="about-course">
|
||||
|
||||
<section class="about-info">
|
||||
<h1>About 6.002x</h1>
|
||||
|
||||
<p>6.002x (Circuits and Electronics) is designed to serve as a first course in an undergraduate electrical engineering (EE), or electrical engineering and computer science (EECS) curriculum. At MIT, 6.002 is in the core of department subjects required for all undergraduates in EECS.</p>
|
||||
|
||||
<p>The course introduces engineering in the context of the lumped circuit abstraction. Topics covered include: resistive elements and networks; independent and dependent sources; switches and MOS transistors; digital abstraction; amplifiers; energy storage elements; dynamics of first- and second-order networks; design in the time and frequency domains; and analog and digital circuits and applications. Design and lab exercises are also significant components of the course. You should expect to spend approximately 10 hours per week on the course.</p>
|
||||
|
||||
</section>
|
||||
|
||||
<section class="on-mitx">
|
||||
<h1>6.002x on <em>MITx</em></h1> <!-- Link doesn't need to be here, but there should be some way to get back to main MITx site -->
|
||||
|
||||
<p>If you successfully complete the course, you will receive an electronic certificate of accomplishment from <em>MITx</em>. This certificate will indicate that you earned it from <em>MITx’s</em> pilot course. In this prototype version, <em>MITx</em> will not require that you be tested in a testing center or otherwise have your identity certified in order to receive this certificate.</p>
|
||||
|
||||
<p>The course uses the textbook Foundations of Analog and Digital Electronic Circuits, by Anant Agarwal and Jeffrey H. Lang. Morgan Kaufmann Publishers, Elsevier, July 2005. While recommended, the book is not required: relevant sections will be provided electronically as part of the online course for personal use in connection with this course only. The copyright for the book is owned by Elsevier. The book can be purchased on <a href="http://www.amazon.com/exec/obidos/ASIN/1558607358/ref=nosim/mitopencourse-20" target="_blank">Amazon</a>.</p>
|
||||
</section>
|
||||
|
||||
<section class="requirements">
|
||||
<h1> Requirements </h1>
|
||||
|
||||
<p>In order to succeed in this course, you must have taken an AP level physics course in electricity and magnetism. You must know basic calculus and linear algebra and have some background in differential equations. Since more advanced mathematics will not show up until the second half of the course, the first half of the course will include an optional remedial differential equations component for those who need it.</p>
|
||||
|
||||
<p>The course web site was developed and tested primarily with
|
||||
Google Chrome. We support current versions of Mozilla Firefox as
|
||||
well. The video player is designed to work with Flash. While we
|
||||
provide a partial non-Flash fallback for the video, as well as
|
||||
partial support for Internet Explorer, other browsers, and
|
||||
tablets, portions of the functionality will be unavailable. </p>
|
||||
</section>
|
||||
|
||||
<section class="cta">
|
||||
<a class="enroll" rel="leanModal" href="/info">View 6.002x Circuits & Electronics as a guest</a>
|
||||
<a class="enroll" rel="leanModal" href="#enroll"><noscript>In order to</noscript> Enroll in 6.002x Circuits & Electronics <noscript>you need to have javascript enabled</noscript></a>
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
<section class="staff">
|
||||
<h1>About the course staff</h1>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<img src="${static.url('staff/agarwal-mit-news-small.jpg')}" alt="Anant Agarwal">
|
||||
<h2>Anant Agarwal</h2>
|
||||
<p>Director of MIT’s Computer Science and Artificial Intelligence Laboratory (CSAIL) and a professor of the Electrical Engineering and Computer Science department at MIT. His research focus is in parallel computer architectures and cloud software systems, and he is a founder of several successful startups, including Tilera, a company that produces scalable multicore processors. Prof. Agarwal won MIT’s Smullin and Jamieson prizes for teaching and co-authored the course textbook “Foundations of Analog and Digital Electronic Circuits.”</p></li>
|
||||
|
||||
<li>
|
||||
<img src="${static.url('staff/gjs-small.jpg')}" alt="Gerald Sussman">
|
||||
<h2>Gerald Sussman</h2>
|
||||
<p>Professor of Electrical Engineering at MIT. He is a well known educator in the computer science community, perhaps best known as the author of Structure and Interpretation of Computer Programs, which is universally acknowledged as one of the top ten textbooks in computer science, and as the creator of Scheme, a popular teaching language. His research spans a range of topics, from artificial intelligence, to physics and chaotic systems, to supercomputer design.</p></li>
|
||||
|
||||
<li>
|
||||
<img src="${static.url('staff/pmitros-small.jpg')}" alt="Piotr Mitros">
|
||||
<h2>Piotr Mitros</h2>
|
||||
<p>Research Scientist at MIT. His research focus is in finding ways to apply techniques from control systems to optimizing the learning process. Dr. Mitros has worked as an analog designer at Texas Instruments, Talking Lights, and most recently, designed the analog front end for a novel medical imaging modality for Rhythmia Medical.</p></li>
|
||||
</ul>
|
||||
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<div id="enroll" class="leanModal_box" name="enroll"><%include file="create_account.html" /></div>
|
||||
|
||||
@@ -17,7 +17,7 @@ $(document).ready(function(){
|
||||
</script>
|
||||
</%block>
|
||||
|
||||
<%block name="title"><title>Course Info - MITx 6.002x</title></%block>
|
||||
<%block name="title"><title>Course Info - edx4edx x</title></%block>
|
||||
|
||||
<%include file="navigation.html" args="active_page='info'" />
|
||||
|
||||
|
||||