diff --git a/brew-formulas.txt b/brew-formulas.txt
index 61e3d33e3a..0aed9645d0 100644
--- a/brew-formulas.txt
+++ b/brew-formulas.txt
@@ -6,3 +6,4 @@ gfortran
python
yuicompressor
node
+graphviz
diff --git a/cms/static/sass/_base.scss b/cms/static/sass/_base.scss
index 2ea98473d1..7d16f0c6f9 100644
--- a/cms/static/sass/_base.scss
+++ b/cms/static/sass/_base.scss
@@ -12,7 +12,6 @@ $bright-blue: #3c8ebf;
$orange: #f96e5b;
$yellow: #fff8af;
$cream: #F6EFD4;
-$mit-red: #933;
$border-color: #ddd;
@mixin hide-text {
diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py
index a99b46fd13..cab57e6819 100644
--- a/common/djangoapps/student/views.py
+++ b/common/djangoapps/student/views.py
@@ -102,7 +102,7 @@ def main_index(request, extra_context={}, user=None):
def course_from_id(course_id):
"""Return the CourseDescriptor corresponding to this course_id"""
course_loc = CourseDescriptor.id_to_location(course_id)
- return modulestore().get_item(course_loc)
+ return modulestore().get_instance(course_id, course_loc)
def press(request):
diff --git a/common/lib/capa/capa/xqueue_interface.py b/common/lib/capa/capa/xqueue_interface.py
index 10f36636b8..2930eb682d 100644
--- a/common/lib/capa/capa/xqueue_interface.py
+++ b/common/lib/capa/capa/xqueue_interface.py
@@ -83,6 +83,9 @@ class XQueueInterface(object):
if error and (msg == 'login_required'): # Log in, then try again
self._login()
+ if files_to_upload is not None:
+ for f in files_to_upload: # Need to rewind file pointers
+ f.seek(0)
(error, msg) = self._send_to_queue(header, body, files_to_upload)
return (error, msg)
diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py
index 4729de905d..a9f29b9a13 100644
--- a/common/lib/xmodule/xmodule/course_module.py
+++ b/common/lib/xmodule/xmodule/course_module.py
@@ -1,6 +1,7 @@
from fs.errors import ResourceNotFoundError
import time
import logging
+import requests
from lxml import etree
from xmodule.util.decorators import lazyproperty
@@ -15,18 +16,44 @@ class CourseDescriptor(SequenceDescriptor):
module_class = SequenceModule
class Textbook:
- def __init__(self, title, table_of_contents_url):
+ def __init__(self, title, book_url):
self.title = title
- self.table_of_contents_url = table_of_contents_url
+ self.book_url = book_url
+ self.table_of_contents = self._get_toc_from_s3()
@classmethod
def from_xml_object(cls, xml_object):
- return cls(xml_object.get('title'), xml_object.get('table_of_contents_url'))
+ return cls(xml_object.get('title'), xml_object.get('book_url'))
@property
def table_of_contents(self):
- raw_table_of_contents = open(self.table_of_contents_url, 'r') # TODO: This will need to come from S3
- table_of_contents = etree.parse(raw_table_of_contents).getroot()
+ return self.table_of_contents
+
+ def _get_toc_from_s3(self):
+ '''
+ Accesses the textbook's table of contents (default name "toc.xml") at the URL self.book_url
+
+ Returns XML tree representation of the table of contents
+ '''
+ toc_url = self.book_url + 'toc.xml'
+
+ # Get the table of contents from S3
+ log.info("Retrieving textbook table of contents from %s" % toc_url)
+ try:
+ r = requests.get(toc_url)
+ except Exception as err:
+ msg = 'Error %s: Unable to retrieve textbook table of contents at %s' % (err, toc_url)
+ log.error(msg)
+ raise Exception(msg)
+
+ # TOC is XML. Parse it
+ try:
+ table_of_contents = etree.fromstring(r.text)
+ except Exception as err:
+ msg = 'Error %s: Unable to parse XML for textbook table of contents at %s' % (err, toc_url)
+ log.error(msg)
+ raise Exception(msg)
+
return table_of_contents
@@ -134,6 +161,10 @@ class CourseDescriptor(SequenceDescriptor):
'all_descriptors' : all_descriptors,}
+ @staticmethod
+ def make_id(org, course, url_name):
+ return '/'.join([org, course, url_name])
+
@staticmethod
def id_to_location(course_id):
'''Convert the given course_id (org/course/name) to a location object.
diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss
index 666ca57800..e08001f6ea 100644
--- a/common/lib/xmodule/xmodule/css/capa/display.scss
+++ b/common/lib/xmodule/xmodule/css/capa/display.scss
@@ -1,12 +1,6 @@
h2 {
margin-top: 0;
margin-bottom: 15px;
- width: flex-grid(2, 9);
- padding-right: flex-gutter(9);
- border-right: 1px dashed #ddd;
- @include box-sizing(border-box);
- display: table-cell;
- vertical-align: top;
&.problem-header {
section.staff {
@@ -15,12 +9,6 @@ h2 {
}
}
- @media screen and (max-width:1120px) {
- display: block;
- width: auto;
- border-right: 0;
- }
-
@media print {
display: block;
width: auto;
@@ -29,16 +17,6 @@ h2 {
}
section.problem {
- display: table-cell;
- width: flex-grid(7, 9);
- padding-left: flex-gutter(9);
-
- @media screen and (max-width:1120px) {
- display: block;
- width: auto;
- padding: 0;
- }
-
@media print {
display: block;
width: auto;
@@ -292,4 +270,10 @@ section.problem {
border: 1px solid #ccc;
padding: lh();
}
+
+ section.action {
+ input.save {
+ @extend .blue-button;
+ }
+ }
}
diff --git a/common/lib/xmodule/xmodule/css/video/display.scss b/common/lib/xmodule/xmodule/css/video/display.scss
index 0b4cf883bf..848294b699 100644
--- a/common/lib/xmodule/xmodule/css/video/display.scss
+++ b/common/lib/xmodule/xmodule/css/video/display.scss
@@ -305,11 +305,11 @@ div.video {
@include box-shadow(0 1px 0 #333);
a.ui-slider-handle {
- background: $mit-red url(../images/slider-handle.png) center center no-repeat;
+ background: $pink url(../images/slider-handle.png) center center no-repeat;
@include background-size(50%);
- border: 1px solid darken($mit-red, 20%);
+ border: 1px solid darken($pink, 20%);
@include border-radius(15px);
- @include box-shadow(inset 0 1px 0 lighten($mit-red, 10%));
+ @include box-shadow(inset 0 1px 0 lighten($pink, 10%));
cursor: pointer;
height: 15px;
left: -6px;
@@ -408,6 +408,7 @@ div.video {
cursor: pointer;
margin-bottom: 8px;
padding: 0;
+ line-height: lh();
&.current {
color: #333;
@@ -415,7 +416,7 @@ div.video {
}
&:hover {
- color: $mit-red;
+ color: $blue;
}
&:empty {
diff --git a/common/lib/xmodule/xmodule/modulestore/__init__.py b/common/lib/xmodule/xmodule/modulestore/__init__.py
index 6c77127d7e..d6dd85deea 100644
--- a/common/lib/xmodule/xmodule/modulestore/__init__.py
+++ b/common/lib/xmodule/xmodule/modulestore/__init__.py
@@ -223,6 +223,13 @@ class ModuleStore(object):
"""
raise NotImplementedError
+ def get_instance(self, course_id, location):
+ """
+ Get an instance of this location, with policy for course_id applied.
+ TODO (vshnayder): this may want to live outside the modulestore eventually
+ """
+ raise NotImplementedError
+
def get_item_errors(self, location):
"""
Return a list of (msg, exception-or-None) errors that the modulestore
@@ -331,7 +338,8 @@ class ModuleStoreBase(ModuleStore):
and datastores.
"""
# check that item is present and raise the promised exceptions if needed
- self.get_item(location)
+ # TODO (vshnayder): post-launch, make errors properties of items
+ #self.get_item(location)
errorlog = self._get_errorlog(location)
return errorlog.errors
diff --git a/common/lib/xmodule/xmodule/modulestore/mongo.py b/common/lib/xmodule/xmodule/modulestore/mongo.py
index caada39f3e..7aa05e474f 100644
--- a/common/lib/xmodule/xmodule/modulestore/mongo.py
+++ b/common/lib/xmodule/xmodule/modulestore/mongo.py
@@ -217,6 +217,13 @@ class MongoModuleStore(ModuleStoreBase):
item = self._find_one(location)
return self._load_items([item], depth)[0]
+ def get_instance(self, course_id, location):
+ """
+ TODO (vshnayder): implement policy tracking in mongo.
+ For now, just delegate to get_item and ignore policy.
+ """
+ return self.get_item(location)
+
def get_items(self, location, depth=0):
items = self.collection.find(
location_to_query(location),
diff --git a/common/lib/xmodule/xmodule/modulestore/xml.py b/common/lib/xmodule/xmodule/modulestore/xml.py
index a77266113f..e0fecb243d 100644
--- a/common/lib/xmodule/xmodule/modulestore/xml.py
+++ b/common/lib/xmodule/xmodule/modulestore/xml.py
@@ -3,6 +3,7 @@ import logging
import os
import re
+from collections import defaultdict
from fs.osfs import OSFS
from importlib import import_module
from lxml import etree
@@ -33,7 +34,7 @@ def clean_out_mako_templating(xml_string):
return xml_string
class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
- def __init__(self, xmlstore, org, course, course_dir,
+ def __init__(self, xmlstore, course_id, course_dir,
policy, error_tracker, **kwargs):
"""
A class that handles loading from xml. Does some munging to ensure that
@@ -43,6 +44,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
"""
self.unnamed_modules = 0
self.used_slugs = set()
+ self.org, self.course, self.url_name = course_id.split('/')
def process_xml(xml):
"""Takes an xml string, and returns a XModuleDescriptor created from
@@ -80,21 +82,24 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
xml_data.set('url_name', slug)
descriptor = XModuleDescriptor.load_from_xml(
- etree.tostring(xml_data), self, org,
- course, xmlstore.default_class)
+ etree.tostring(xml_data), self, self.org,
+ self.course, xmlstore.default_class)
#log.debug('==> importing descriptor location %s' %
# repr(descriptor.location))
descriptor.metadata['data_dir'] = course_dir
- xmlstore.modules[descriptor.location] = descriptor
+ xmlstore.modules[course_id][descriptor.location] = descriptor
if xmlstore.eager:
descriptor.get_children()
return descriptor
render_template = lambda: ''
- load_item = xmlstore.get_item
+ # TODO (vshnayder): we are somewhat architecturally confused in the loading code:
+ # load_item should actually be get_instance, because it expects the course-specific
+ # policy to be loaded. For now, just add the course_id here...
+ load_item = lambda location: xmlstore.get_instance(course_id, location)
resources_fs = OSFS(xmlstore.data_dir / course_dir)
MakoDescriptorSystem.__init__(self, load_item, resources_fs,
@@ -127,7 +132,7 @@ class XMLModuleStore(ModuleStoreBase):
self.eager = eager
self.data_dir = path(data_dir)
- self.modules = {} # location -> XModuleDescriptor
+ self.modules = defaultdict(dict) # course_id -> dict(location -> XModuleDescriptor)
self.courses = {} # course_dir -> XModuleDescriptor for the course
if default_class is None:
@@ -236,14 +241,24 @@ class XMLModuleStore(ModuleStoreBase):
tracker(msg)
course = course_dir
- url_name = course_data.get('url_name')
+ url_name = course_data.get('url_name', course_data.get('slug'))
if url_name:
policy_path = self.data_dir / course_dir / 'policies' / '{0}.json'.format(url_name)
policy = self.load_policy(policy_path, tracker)
else:
policy = {}
+ # VS[compat] : 'name' is deprecated, but support it for now...
+ if course_data.get('name'):
+ url_name = Location.clean(course_data.get('name'))
+ tracker("'name' is deprecated for module xml. Please use "
+ "display_name and url_name.")
+ else:
+ raise ValueError("Can't load a course without a 'url_name' "
+ "(or 'name') set. Set url_name.")
- system = ImportSystem(self, org, course, course_dir, policy, tracker)
+
+ course_id = CourseDescriptor.make_id(org, course, url_name)
+ system = ImportSystem(self, course_id, course_dir, policy, tracker)
course_descriptor = system.process_xml(etree.tostring(course_data))
@@ -257,6 +272,27 @@ class XMLModuleStore(ModuleStoreBase):
return course_descriptor
+ def get_instance(self, course_id, location, depth=0):
+ """
+ Returns an XModuleDescriptor instance for the item at
+ location, with the policy for course_id. (In case two xml
+ dirs have different content at the same location, return the
+ one for this course_id.)
+
+ If any segment of the location is None except revision, raises
+ xmodule.modulestore.exceptions.InsufficientSpecificationError
+
+ If no object is found at that location, raises
+ xmodule.modulestore.exceptions.ItemNotFoundError
+
+ location: Something that can be passed to Location
+ """
+ location = Location(location)
+ try:
+ return self.modules[course_id][location]
+ except KeyError:
+ raise ItemNotFoundError(location)
+
def get_item(self, location, depth=0):
"""
Returns an XModuleDescriptor instance for the item at location.
@@ -271,11 +307,8 @@ class XMLModuleStore(ModuleStoreBase):
location: Something that can be passed to Location
"""
- location = Location(location)
- try:
- return self.modules[location]
- except KeyError:
- raise ItemNotFoundError(location)
+ raise NotImplementedError("XMLModuleStores can't guarantee that definitions"
+ " are unique. Use get_instance.")
def get_courses(self, depth=0):
diff --git a/common/lib/xmodule/xmodule/modulestore/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py
index 891db7e994..89f94d8cdb 100644
--- a/common/lib/xmodule/xmodule/modulestore/xml_importer.py
+++ b/common/lib/xmodule/xmodule/modulestore/xml_importer.py
@@ -22,21 +22,22 @@ def import_from_xml(store, data_dir, course_dirs=None, eager=True,
eager=eager,
course_dirs=course_dirs
)
- for module in module_store.modules.itervalues():
+ for course_id in module_store.modules.keys():
+ for module in module_store.modules[course_id].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:
- store.create_item(module.location)
- except DuplicateItemError:
- log.exception('Item already exists at %s' % module.location.url())
- pass
- if 'data' in module.definition:
- store.update_item(module.location, module.definition['data'])
- if 'children' in module.definition:
- store.update_children(module.location, module.definition['children'])
- # NOTE: It's important to use own_metadata here to avoid writing
- # inherited metadata everywhere.
- store.update_metadata(module.location, dict(module.own_metadata))
+ # TODO (cpennington): This forces import to overrite the same items.
+ # This should in the future create new revisions of the items on import
+ try:
+ store.create_item(module.location)
+ except DuplicateItemError:
+ log.exception('Item already exists at %s' % module.location.url())
+ pass
+ if 'data' in module.definition:
+ store.update_item(module.location, module.definition['data'])
+ if 'children' in module.definition:
+ store.update_children(module.location, module.definition['children'])
+ # NOTE: It's important to use own_metadata here to avoid writing
+ # inherited metadata everywhere.
+ store.update_metadata(module.location, dict(module.own_metadata))
return module_store
diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py
index a3eac0258e..2a380bb8be 100644
--- a/common/lib/xmodule/xmodule/tests/__init__.py
+++ b/common/lib/xmodule/xmodule/tests/__init__.py
@@ -8,6 +8,7 @@
import unittest
import os
import fs
+import fs.osfs
import json
import json
diff --git a/common/lib/xmodule/xmodule/tests/test_export.py b/common/lib/xmodule/xmodule/tests/test_export.py
index 268c3d1062..2520d95937 100644
--- a/common/lib/xmodule/xmodule/tests/test_export.py
+++ b/common/lib/xmodule/xmodule/tests/test_export.py
@@ -84,20 +84,22 @@ class RoundTripTestCase(unittest.TestCase):
strip_filenames(exported_course)
self.assertEquals(initial_course, exported_course)
+ self.assertEquals(initial_course.id, exported_course.id)
+ course_id = initial_course.id
print "Checking key equality"
- self.assertEquals(sorted(initial_import.modules.keys()),
- sorted(second_import.modules.keys()))
+ self.assertEquals(sorted(initial_import.modules[course_id].keys()),
+ sorted(second_import.modules[course_id].keys()))
print "Checking module equality"
- for location in initial_import.modules.keys():
+ for location in initial_import.modules[course_id].keys():
print "Checking", location
if location.category == 'html':
print ("Skipping html modules--they can't import in"
" final form without writing files...")
continue
- self.assertEquals(initial_import.modules[location],
- second_import.modules[location])
+ self.assertEquals(initial_import.modules[course_id][location],
+ second_import.modules[course_id][location])
def setUp(self):
diff --git a/common/lib/xmodule/xmodule/tests/test_import.py b/common/lib/xmodule/xmodule/tests/test_import.py
index 2061b63fb6..34e4767a62 100644
--- a/common/lib/xmodule/xmodule/tests/test_import.py
+++ b/common/lib/xmodule/xmodule/tests/test_import.py
@@ -207,3 +207,48 @@ class ImportTestCase(unittest.TestCase):
check_for_key(key, c)
check_for_key('graceperiod', course)
+
+
+ def test_policy_loading(self):
+ """Make sure that when two courses share content with the same
+ org and course names, policy applies to the right one."""
+
+ def get_course(name):
+ print "Importing {0}".format(name)
+
+ modulestore = XMLModuleStore(DATA_DIR, eager=True, course_dirs=[name])
+ courses = modulestore.get_courses()
+ self.assertEquals(len(courses), 1)
+ return courses[0]
+
+ toy = get_course('toy')
+ two_toys = get_course('two_toys')
+
+ self.assertEqual(toy.url_name, "2012_Fall")
+ self.assertEqual(two_toys.url_name, "TT_2012_Fall")
+
+ toy_ch = toy.get_children()[0]
+ two_toys_ch = two_toys.get_children()[0]
+
+ self.assertEqual(toy_ch.display_name, "Overview")
+ self.assertEqual(two_toys_ch.display_name, "Two Toy Overview")
+
+
+ def test_definition_loading(self):
+ """When two courses share the same org and course name and
+ both have a module with the same url_name, the definitions shouldn't clash.
+
+ TODO (vshnayder): once we have a CMS, this shouldn't
+ happen--locations should uniquely name definitions. But in
+ our imperfect XML world, it can (and likely will) happen."""
+
+ modulestore = XMLModuleStore(DATA_DIR, eager=True, course_dirs=['toy', 'two_toys'])
+
+ toy_id = "edX/toy/2012_Fall"
+ two_toy_id = "edX/toy/TT_2012_Fall"
+
+ location = Location(["i4x", "edX", "toy", "video", "Welcome"])
+ toy_video = modulestore.get_instance(toy_id, location)
+ two_toy_video = modulestore.get_instance(two_toy_id, location)
+ self.assertEqual(toy_video.metadata['youtube'], "1.0:p2Q6BrNhdh8")
+ self.assertEqual(two_toy_video.metadata['youtube'], "1.0:p2Q6BrNhdh9")
diff --git a/common/test/data/two_toys/README b/common/test/data/two_toys/README
new file mode 100644
index 0000000000..ef1a501ee8
--- /dev/null
+++ b/common/test/data/two_toys/README
@@ -0,0 +1 @@
+A copy of the toy course, with different metadata. Used to test policy loading for course with identical org course fields and shared content.
diff --git a/common/test/data/two_toys/chapter/Overview.xml b/common/test/data/two_toys/chapter/Overview.xml
new file mode 100644
index 0000000000..a738a9eec3
--- /dev/null
+++ b/common/test/data/two_toys/chapter/Overview.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/common/test/data/two_toys/course.xml b/common/test/data/two_toys/course.xml
new file mode 100644
index 0000000000..655859ab8b
--- /dev/null
+++ b/common/test/data/two_toys/course.xml
@@ -0,0 +1 @@
+
diff --git a/common/test/data/two_toys/course/TT_2012_Fall.xml b/common/test/data/two_toys/course/TT_2012_Fall.xml
new file mode 100644
index 0000000000..b6dbd48b4c
--- /dev/null
+++ b/common/test/data/two_toys/course/TT_2012_Fall.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/common/test/data/two_toys/policies/TT_2012_Fall.json b/common/test/data/two_toys/policies/TT_2012_Fall.json
new file mode 100644
index 0000000000..2831767b32
--- /dev/null
+++ b/common/test/data/two_toys/policies/TT_2012_Fall.json
@@ -0,0 +1,23 @@
+{
+ "course/TT_2012_Fall": {
+ "graceperiod": "2 days 5 hours 59 minutes 59 seconds",
+ "start": "2015-07-17T12:00",
+ "display_name": "Two Toys Course"
+ },
+ "chapter/Overview": {
+ "display_name": "Two Toy Overview"
+ },
+ "videosequence/Toy_Videos": {
+ "display_name": "Toy Videos",
+ "format": "Lecture Sequence"
+ },
+ "html/toylab": {
+ "display_name": "Toy lab"
+ },
+ "video/Video_Resources": {
+ "display_name": "Video Resources"
+ },
+ "video/Welcome": {
+ "display_name": "Welcome"
+ }
+}
diff --git a/common/test/data/two_toys/video/Video_Resources.xml b/common/test/data/two_toys/video/Video_Resources.xml
new file mode 100644
index 0000000000..88657ed79f
--- /dev/null
+++ b/common/test/data/two_toys/video/Video_Resources.xml
@@ -0,0 +1 @@
+
diff --git a/common/test/data/two_toys/video/Welcome.xml b/common/test/data/two_toys/video/Welcome.xml
new file mode 100644
index 0000000000..7f7e238c10
--- /dev/null
+++ b/common/test/data/two_toys/video/Welcome.xml
@@ -0,0 +1 @@
+
diff --git a/common/test/data/two_toys/videosequence/Toy_Videos.xml b/common/test/data/two_toys/videosequence/Toy_Videos.xml
new file mode 100644
index 0000000000..cf8b2defc5
--- /dev/null
+++ b/common/test/data/two_toys/videosequence/Toy_Videos.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/create-dev-env.sh b/create-dev-env.sh
index 7d8fdd2f17..96f212c9b5 100755
--- a/create-dev-env.sh
+++ b/create-dev-env.sh
@@ -105,7 +105,7 @@ NUMPY_VER="1.6.2"
SCIPY_VER="0.10.1"
BREW_FILE="$BASE/mitx/brew-formulas.txt"
LOG="/var/tmp/install-$(date +%Y%m%d-%H%M%S).log"
-APT_PKGS="curl git python-virtualenv build-essential python-dev gfortran liblapack-dev libfreetype6-dev libpng12-dev libxml2-dev libxslt-dev yui-compressor coffeescript"
+APT_PKGS="curl git python-virtualenv build-essential python-dev gfortran liblapack-dev libfreetype6-dev libpng12-dev libxml2-dev libxslt-dev yui-compressor coffeescript graphviz"
if [[ $EUID -eq 0 ]]; then
error "This script should not be run using sudo or as the root user"
diff --git a/install.txt b/install.txt
index fa82b11a5c..37a6e50986 100644
--- a/install.txt
+++ b/install.txt
@@ -74,5 +74,4 @@ There is also a script "create-dev-env.sh" that automates these steps.
$ django-admin.py syncdb --settings=envs.dev --pythonpath=.
$ django-admin.py migrate --settings=envs.dev --pythonpath=.
$ django-admin.py runserver --settings=envs.dev --pythonpath=.
-
diff --git a/lms/djangoapps/course_wiki/tests/tests.py b/lms/djangoapps/course_wiki/tests/tests.py
index e004265379..f1c4fa4810 100644
--- a/lms/djangoapps/course_wiki/tests/tests.py
+++ b/lms/djangoapps/course_wiki/tests/tests.py
@@ -36,7 +36,7 @@ class WikiRedirectTestCase(PageLoader):
"""
Test that requesting wiki URLs redirect properly to or out of classes.
- An enrolled in student going from /courses/edX/toy/2012_Fall/profile
+ An enrolled in student going from /courses/edX/toy/2012_Fall/progress
to /wiki/some/fake/wiki/page/ will redirect to
/courses/edX/toy/2012_Fall/wiki/some/fake/wiki/page/
@@ -48,10 +48,10 @@ class WikiRedirectTestCase(PageLoader):
self.enroll(self.toy)
- referer = reverse("profile", kwargs={ 'course_id' : self.toy.id })
+ referer = reverse("progress", kwargs={ 'course_id' : self.toy.id })
destination = reverse("wiki:get", kwargs={'path': 'some/fake/wiki/page/'})
- redirected_to = referer.replace("profile", "wiki/some/fake/wiki/page/")
+ redirected_to = referer.replace("progress", "wiki/some/fake/wiki/page/")
resp = self.client.get( destination, HTTP_REFERER=referer)
self.assertEqual(resp.status_code, 302 )
@@ -77,11 +77,11 @@ class WikiRedirectTestCase(PageLoader):
"""
course_wiki_home = reverse('course_wiki', kwargs={'course_id' : course.id})
- referer = reverse("profile", kwargs={ 'course_id' : self.toy.id })
+ referer = reverse("progress", kwargs={ 'course_id' : self.toy.id })
resp = self.client.get(course_wiki_home, follow=True, HTTP_REFERER=referer)
- course_wiki_page = referer.replace('profile', 'wiki/' + self.toy.wiki_slug + "/")
+ course_wiki_page = referer.replace('progress', 'wiki/' + self.toy.wiki_slug + "/")
ending_location = resp.redirect_chain[-1][0]
ending_status = resp.redirect_chain[-1][1]
diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py
index f0b82a3c9c..b4407b7f93 100644
--- a/lms/djangoapps/courseware/courses.py
+++ b/lms/djangoapps/courseware/courses.py
@@ -25,7 +25,7 @@ def get_course_by_id(course_id):
"""
try:
course_loc = CourseDescriptor.id_to_location(course_id)
- return modulestore().get_item(course_loc)
+ return modulestore().get_instance(course_id, course_loc)
except (KeyError, ItemNotFoundError):
raise Http404("Course not found.")
diff --git a/lms/djangoapps/courseware/grades.py b/lms/djangoapps/courseware/grades.py
index 8417bf9a48..951c48822e 100644
--- a/lms/djangoapps/courseware/grades.py
+++ b/lms/djangoapps/courseware/grades.py
@@ -9,6 +9,7 @@ from django.conf import settings
from models import StudentModuleCache
from module_render import get_module, get_instance_module
from xmodule import graders
+from xmodule.course_module import CourseDescriptor
from xmodule.graders import Score
from models import StudentModule
@@ -63,8 +64,10 @@ def grade(student, request, course, student_module_cache=None):
scores = []
# TODO: We need the request to pass into here. If we could forgo that, our arguments
# would be simpler
+ course_id = CourseDescriptor.location_to_id(course.location)
section_module = get_module(student, request,
- section_descriptor.location, student_module_cache)
+ section_descriptor.location, student_module_cache,
+ course_id)
if section_module is None:
# student doesn't have access to this module, or something else
# went wrong.
diff --git a/lms/djangoapps/courseware/management/commands/check_course.py b/lms/djangoapps/courseware/management/commands/check_course.py
index 80f4b57983..4b44581c3f 100644
--- a/lms/djangoapps/courseware/management/commands/check_course.py
+++ b/lms/djangoapps/courseware/management/commands/check_course.py
@@ -1,5 +1,7 @@
import os.path
+# THIS COMMAND IS OUT OF DATE
+
from lxml import etree
from django.core.management.base import BaseCommand
@@ -72,13 +74,17 @@ class Command(BaseCommand):
# TODO: use args as list of files to check. Fix loading to work for other files.
+ print "This command needs updating before use"
+ return
+"""
sample_user = User.objects.all()[0]
print "Attempting to load courseware"
# TODO (cpennington): Get coursename in a legitimate way
course_location = 'i4x://edx/6002xs12/course/6.002_Spring_2012'
- student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(sample_user, modulestore().get_item(course_location))
+ student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
+ sample_user, modulestore().get_item(course_location))
course = get_module(sample_user, None, course_location, student_module_cache)
to_run = [
@@ -96,3 +102,4 @@ class Command(BaseCommand):
print 'Courseware passes all checks!'
else:
print "Courseware fails some checks"
+"""
diff --git a/lms/djangoapps/courseware/management/commands/clean_xml.py b/lms/djangoapps/courseware/management/commands/clean_xml.py
index de845df572..9fd52178a2 100644
--- a/lms/djangoapps/courseware/management/commands/clean_xml.py
+++ b/lms/djangoapps/courseware/management/commands/clean_xml.py
@@ -124,9 +124,11 @@ def check_roundtrip(course_dir):
print "======== ideally there is no diff above this ======="
-def clean_xml(course_dir, export_dir):
+def clean_xml(course_dir, export_dir, force):
(ok, course) = import_with_checks(course_dir)
- if ok:
+ if ok or force:
+ if not ok:
+ print "WARNING: Exporting despite errors"
export(course, export_dir)
check_roundtrip(export_dir)
else:
@@ -138,11 +140,18 @@ class Command(BaseCommand):
help = """Imports specified course.xml, validate it, then exports in
a canonical format.
-Usage: clean_xml PATH-TO-COURSE-DIR PATH-TO-OUTPUT-DIR
+Usage: clean_xml PATH-TO-COURSE-DIR PATH-TO-OUTPUT-DIR [force]
+
+If 'force' is specified as the last argument, exports even if there
+were import errors.
"""
def handle(self, *args, **options):
- if len(args) != 2:
+ n = len(args)
+ if n < 2 or n > 3:
print Command.help
return
- clean_xml(args[0], args[1])
+ force = False
+ if n == 3 and args[2] == 'force':
+ force = True
+ clean_xml(args[0], args[1], force)
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index 3d6a65595d..fbd43b8247 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -71,13 +71,13 @@ def toc_for_course(user, request, course, active_chapter, active_section, course
'''
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(user, course, depth=2)
- course = get_module(user, request, course.location, student_module_cache, course_id=course_id)
+ course = get_module(user, request, course.location, student_module_cache, course_id)
chapters = list()
for chapter in course.get_display_items():
hide_from_toc = chapter.metadata.get('hide_from_toc','false').lower() == 'true'
if hide_from_toc:
- continue
+ continue
sections = list()
for section in chapter.get_display_items():
@@ -131,7 +131,7 @@ def get_section(course_module, chapter, section):
return section_module
-def get_module(user, request, location, student_module_cache, position=None, course_id=None):
+def get_module(user, request, location, student_module_cache, course_id, position=None):
''' Get an instance of the xmodule class identified by location,
setting the state based on an existing StudentModule, or creating one if none
exists.
@@ -141,21 +141,14 @@ def get_module(user, request, location, student_module_cache, position=None, cou
- request : current django HTTPrequest
- location : A Location-like object identifying the module to load
- student_module_cache : a StudentModuleCache
+ - course_id : the course_id in the context of which to load module
- position : extra information from URL for user-specified
position within module
Returns: xmodule instance
'''
- descriptor = modulestore().get_item(location)
-
- # NOTE:
- # A 'course_id' is understood to be the triplet (org, course, run), for example
- # (MITx, 6.002x, 2012_Spring).
- # At the moment generic XModule does not contain enough information to replicate
- # the triplet (it is missing 'run'), so we must pass down course_id
- if course_id is None:
- course_id = descriptor.location.course_id # Will NOT produce (org, course, run) for non-CourseModule's
+ descriptor = modulestore().get_instance(course_id, location)
# Short circuit--if the user shouldn't have access, bail without doing any work
if not has_access(user, descriptor, 'load'):
@@ -181,7 +174,7 @@ def get_module(user, request, location, student_module_cache, position=None, cou
# Setup system context for module instance
ajax_url = reverse('modx_dispatch',
kwargs=dict(course_id=course_id,
- id=descriptor.location.url(),
+ location=descriptor.location.url(),
dispatch=''),
)
@@ -208,7 +201,7 @@ def get_module(user, request, location, student_module_cache, position=None, cou
Delegate to get_module. It does an access check, so may return None
"""
return get_module(user, request, location,
- student_module_cache, position, course_id=course_id)
+ student_module_cache, course_id, position)
# TODO (cpennington): When modules are shared between courses, the static
# prefix is going to have to be specific to the module, not the directory
@@ -276,7 +269,7 @@ def get_instance_module(user, module, student_module_cache):
else:
return None
-def get_shared_instance_module(user, module, student_module_cache):
+def get_shared_instance_module(course_id, user, module, student_module_cache):
"""
Return shared_module is a StudentModule specific to all modules with the same
'shared_state_key' attribute, or None if the module does not elect to
@@ -284,7 +277,7 @@ def get_shared_instance_module(user, module, student_module_cache):
"""
if user.is_authenticated():
# To get the shared_state_key, we need to descriptor
- descriptor = modulestore().get_item(module.location)
+ descriptor = modulestore().get_instance(course_id, module.location)
shared_state_key = getattr(module, 'shared_state_key', None)
if shared_state_key is not None:
@@ -325,8 +318,8 @@ def xqueue_callback(request, course_id, userid, id, dispatch):
user = User.objects.get(id=userid)
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
- user, modulestore().get_item(id), depth=0, select_for_update=True)
- instance = get_module(user, request, id, student_module_cache)
+ user, modulestore().get_instance(course_id, id), depth=0, select_for_update=True)
+ instance = get_module(user, request, id, student_module_cache, course_id)
if instance is None:
log.debug("No module {0} for user {1}--access denied?".format(id, user))
raise Http404
@@ -362,7 +355,7 @@ def xqueue_callback(request, course_id, userid, id, dispatch):
return HttpResponse("")
-def modx_dispatch(request, dispatch=None, id=None, course_id=None):
+def modx_dispatch(request, dispatch, location, course_id):
''' Generic view for extensions. This is where AJAX calls go.
Arguments:
@@ -371,7 +364,8 @@ def modx_dispatch(request, dispatch=None, id=None, course_id=None):
- dispatch -- the command string to pass through to the module's handle_ajax call
(e.g. 'problem_reset'). If this string contains '?', only pass
through the part before the first '?'.
- - id -- the module id. Used to look up the XModule instance
+ - location -- the module location. Used to look up the XModule instance
+ - course_id -- defines the course context for this request.
'''
# ''' (fix emacs broken parsing)
@@ -380,6 +374,12 @@ def modx_dispatch(request, dispatch=None, id=None, course_id=None):
if request.FILES:
for fileinput_id in request.FILES.keys():
inputfiles = request.FILES.getlist(fileinput_id)
+
+ if len(inputfiles) > settings.MAX_FILEUPLOADS_PER_INPUT:
+ too_many_files_msg = 'Submission aborted! Maximum %d files may be submitted at once' %\
+ settings.MAX_FILEUPLOADS_PER_INPUT
+ return HttpResponse(json.dumps({'success': too_many_files_msg}))
+
for inputfile in inputfiles:
if inputfile.size > settings.STUDENT_FILEUPLOAD_MAX_SIZE: # Bytes
file_too_big_msg = 'Submission aborted! Your file "%s" is too large (max size: %d MB)' %\
@@ -387,16 +387,18 @@ def modx_dispatch(request, dispatch=None, id=None, course_id=None):
return HttpResponse(json.dumps({'success': file_too_big_msg}))
p[fileinput_id] = inputfiles
- student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(request.user, modulestore().get_item(id))
- instance = get_module(request.user, request, id, student_module_cache, course_id=course_id)
+ student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
+ request.user, modulestore().get_instance(course_id, location))
+
+ instance = get_module(request.user, request, location, student_module_cache, course_id)
if instance is None:
# Either permissions just changed, or someone is trying to be clever
# and load something they shouldn't have access to.
- log.debug("No module {0} for user {1}--access denied?".format(id, user))
+ log.debug("No module {0} for user {1}--access denied?".format(location, user))
raise Http404
instance_module = get_instance_module(request.user, instance, student_module_cache)
- shared_module = get_shared_instance_module(request.user, instance, student_module_cache)
+ shared_module = get_shared_instance_module(course_id, request.user, instance, student_module_cache)
# Don't track state for anonymous users (who don't have student modules)
if instance_module is not None:
diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py
index da518f45b7..bb48dce609 100644
--- a/lms/djangoapps/courseware/tests/tests.py
+++ b/lms/djangoapps/courseware/tests/tests.py
@@ -303,7 +303,7 @@ class TestViewAuth(PageLoader):
'instructor_dashboard',
'gradebook',
'grade_summary',)]
- urls.append(reverse('student_profile', kwargs={'course_id': course.id,
+ urls.append(reverse('student_progress', kwargs={'course_id': course.id,
'student_id': user(self.student).id}))
return urls
@@ -388,7 +388,7 @@ class TestViewAuth(PageLoader):
list of urls that students should be able to see only
after launch, but staff should see before
"""
- urls = reverse_urls(['info', 'courseware', 'profile'], course)
+ urls = reverse_urls(['info', 'courseware', 'progress'], course)
urls.extend([
reverse('book', kwargs={'course_id': course.id, 'book_index': book.title})
for book in course.textbooks
@@ -411,7 +411,7 @@ class TestViewAuth(PageLoader):
"""list of urls that only instructors/staff should be able to see"""
urls = reverse_urls(['instructor_dashboard','gradebook','grade_summary'],
course)
- urls.append(reverse('student_profile', kwargs={'course_id': course.id,
+ urls.append(reverse('student_progress', kwargs={'course_id': course.id,
'student_id': user(self.student).id}))
return urls
diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py
index a28642a4c1..583628d1f2 100644
--- a/lms/djangoapps/courseware/views.py
+++ b/lms/djangoapps/courseware/views.py
@@ -78,7 +78,7 @@ def courses(request):
'''
Render "find courses" page. The course selection work is done in courseware.courses.
'''
- universities = get_courses_by_university(request.user,
+ universities = get_courses_by_university(request.user,
domain=request.META.get('HTTP_HOST'))
return render_to_response("courses.html", {'universities': universities})
@@ -153,7 +153,7 @@ def index(request, course_id, chapter=None, section=None,
section_descriptor)
module = get_module(request.user, request,
section_descriptor.location,
- student_module_cache, course_id=course_id)
+ student_module_cache, course_id)
if module is None:
# User is probably being clever and trying to access something
# they don't have access to.
@@ -168,7 +168,7 @@ def index(request, course_id, chapter=None, section=None,
# Add a list of all the errors...
context['course_errors'] = modulestore().get_item_errors(course.location)
- result = render_to_response('courseware.html', context)
+ result = render_to_response('courseware/courseware.html', context)
except:
# In production, don't want to let a 500 out for any reason
if settings.DEBUG:
@@ -184,8 +184,9 @@ def index(request, course_id, chapter=None, section=None,
position=position
))
try:
- result = render_to_response('courseware-error.html',
- {'staff_access': staff_access})
+ result = render_to_response('courseware/courseware-error.html',
+ {'staff_access': staff_access,
+ 'course' : course})
except:
result = HttpResponse("There was an unrecoverable error")
@@ -229,7 +230,7 @@ def course_info(request, course_id):
course = get_course_with_access(request.user, course_id, 'load')
staff_access = has_access(request.user, course, 'staff')
- return render_to_response('info.html', {'course': course,
+ return render_to_response('courseware/info.html', {'course': course,
'staff_access': staff_access,})
@@ -292,11 +293,10 @@ def news(request, course_id):
@login_required
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
-def profile(request, course_id, student_id=None):
- """ User profile. Show username, location, etc, as well as grades .
- We need to allow the user to change some of these settings.
+def progress(request, course_id, student_id=None):
+ """ User progress. We show the grade bar and every problem score.
- Course staff are allowed to see the profiles of students in their class.
+ Course staff are allowed to see the progress of students in their class.
"""
course = get_course_with_access(request.user, course_id, 'load')
staff_access = has_access(request.user, course, 'staff')
@@ -310,28 +310,22 @@ def profile(request, course_id, student_id=None):
raise Http404
student = User.objects.get(id=int(student_id))
- user_info = UserProfile.objects.get(user=student)
-
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(request.user, course)
- course_module = get_module(request.user, request, course.location, student_module_cache, course_id=course_id)
+ course_module = get_module(request.user, request, course.location,
+ student_module_cache, course_id)
- courseware_summary = grades.progress_summary(student, course_module, course.grader, student_module_cache)
+ courseware_summary = grades.progress_summary(student, course_module,
+ course.grader, student_module_cache)
grade_summary = grades.grade(request.user, request, course, student_module_cache)
- context = {'name': user_info.name,
- 'username': student.username,
- 'location': user_info.location,
- 'language': user_info.language,
- 'email': student.email,
- 'course': course,
- 'csrf': csrf(request)['csrf_token'],
+ context = {'course': course,
'courseware_summary': courseware_summary,
'grade_summary': grade_summary,
'staff_access': staff_access,
}
context.update()
- return render_to_response('profile.html', context)
+ return render_to_response('courseware/progress.html', context)
@@ -359,7 +353,7 @@ def gradebook(request, course_id):
}
for student in enrolled_students]
- return render_to_response('gradebook.html', {'students': student_info,
+ return render_to_response('courseware/gradebook.html', {'students': student_info,
'course': course,
'course_id': course_id,
# Checked above
@@ -374,7 +368,7 @@ def grade_summary(request, course_id):
# For now, just a static page
context = {'course': course,
'staff_access': True,}
- return render_to_response('grade_summary.html', context)
+ return render_to_response('courseware/grade_summary.html', context)
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@@ -385,4 +379,4 @@ def instructor_dashboard(request, course_id):
# For now, just a static page
context = {'course': course,
'staff_access': True,}
- return render_to_response('instructor_dashboard.html', context)
+ return render_to_response('courseware/instructor_dashboard.html', context)
diff --git a/lms/djangoapps/django_comment_client/management/commands/seed_permissions_roles.py b/lms/djangoapps/django_comment_client/management/commands/seed_permissions_roles.py
index 8ce0cf49d3..5987d5c677 100644
--- a/lms/djangoapps/django_comment_client/management/commands/seed_permissions_roles.py
+++ b/lms/djangoapps/django_comment_client/management/commands/seed_permissions_roles.py
@@ -3,13 +3,16 @@ from django_comment_client.models import Permission, Role
class Command(BaseCommand):
- args = ''
+ args = 'course_id'
help = 'Seed default permisssions and roles'
def handle(self, *args, **options):
- administrator_role = Role.objects.get_or_create(name="Administrator", course_id="MITx/6.002x/2012_Fall")[0]
- moderator_role = Role.objects.get_or_create(name="Moderator", course_id="MITx/6.002x/2012_Fall")[0]
- student_role = Role.objects.get_or_create(name="Student", course_id="MITx/6.002x/2012_Fall")[0]
+ if len(args) != 1:
+ raise CommandError("The number of arguments does not match. ")
+ course_id = args[0]
+ administrator_role = Role.objects.get_or_create(name="Administrator", course_id=course_id)[0]
+ moderator_role = Role.objects.get_or_create(name="Moderator", course_id=course_id)[0]
+ student_role = Role.objects.get_or_create(name="Student", course_id=course_id)[0]
for per in ["vote", "update_thread", "follow_thread", "unfollow_thread",
"update_comment", "create_sub_comment", "unvote" , "create_thread",
diff --git a/lms/djangoapps/staticbook/views.py b/lms/djangoapps/staticbook/views.py
index aaafb60dd8..d68117dd8a 100644
--- a/lms/djangoapps/staticbook/views.py
+++ b/lms/djangoapps/staticbook/views.py
@@ -1,3 +1,4 @@
+from django.conf import settings
from django.contrib.auth.decorators import login_required
from mitxmako.shortcuts import render_to_response
@@ -14,7 +15,7 @@ def index(request, course_id, book_index, page=0):
table_of_contents = textbook.table_of_contents
return render_to_response('staticbook.html',
- {'page': int(page), 'course': course,
+ {'page': int(page), 'course': course, 'book_url': textbook.book_url,
'table_of_contents': table_of_contents,
'staff_access': staff_access})
diff --git a/lms/envs/aws.py b/lms/envs/aws.py
index d2d71830b0..059254bdff 100644
--- a/lms/envs/aws.py
+++ b/lms/envs/aws.py
@@ -19,6 +19,11 @@ EMAIL_BACKEND = 'django_ses.SESBackend'
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
+# Disable askbot, enable Berkeley forums
+MITX_FEATURES['ENABLE_DISCUSSION'] = False
+MITX_FEATURES['ENABLE_DISCUSSION_SERVICE'] = True
+
+
########################### NON-SECURE ENV CONFIG ##############################
# Things like server locations, ports, etc.
with open(ENV_ROOT / "env.json") as env_file:
@@ -60,3 +65,5 @@ XQUEUE_INTERFACE = AUTH_TOKENS['XQUEUE_INTERFACE']
if 'COURSE_ID' in ENV_TOKENS:
ASKBOT_URL = "courses/{0}/discussions/".format(ENV_TOKENS['COURSE_ID'])
+COMMENTS_SERVICE_URL = ENV_TOKENS["COMMENTS_SERVICE_URL"]
+
diff --git a/lms/envs/common.py b/lms/envs/common.py
index 758844ba5f..408e770072 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -146,6 +146,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
)
STUDENT_FILEUPLOAD_MAX_SIZE = 4*1000*1000 # 4 MB
+MAX_FILEUPLOADS_PER_INPUT = 10
# FIXME:
# We should have separate S3 staged URLs in case we need to make changes to
diff --git a/lms/lib/comment_client/settings.py b/lms/lib/comment_client/settings.py
index df896204ce..f64726335f 100644
--- a/lms/lib/comment_client/settings.py
+++ b/lms/lib/comment_client/settings.py
@@ -1,3 +1,8 @@
-SERVICE_HOST = 'http://localhost:4567'
+from django.conf import settings
+
+if hasattr(settings, "COMMENTS_SERVICE_URL"):
+ SERVICE_HOST = settings.COMMENTS_SERVICE_URL
+else:
+ SERVICE_HOST = 'http://localhost:4567'
PREFIX = SERVICE_HOST + '/api/v1'
diff --git a/lms/static/sass/base/_variables.scss b/lms/static/sass/base/_variables.scss
index 8baa2f2d70..755a4d0639 100644
--- a/lms/static/sass/base/_variables.scss
+++ b/lms/static/sass/base/_variables.scss
@@ -27,5 +27,4 @@ $border-color: #C8C8C8;
$light-gray: #ddd;
$dark-gray: #333;
-$mit-red: #993333;
$text-color: $dark-gray;
diff --git a/lms/static/sass/course/base/_base.scss b/lms/static/sass/course/base/_base.scss
index 952ff91365..dffad7d347 100644
--- a/lms/static/sass/course/base/_base.scss
+++ b/lms/static/sass/course/base/_base.scss
@@ -53,6 +53,23 @@ input[type="password"] {
}
}
+label {
+ font-weight: normal;
+ font-style: normal;
+}
+
+input[type="reset"],
+input[type="submit"],
+input[type="button"],
+button,
+.button {
+ @extend .gray-button;
+
+ form & {
+ @extend .gray-button;
+ }
+}
+
img {
max-width: 100%;
diff --git a/lms/static/sass/course/base/_extends.scss b/lms/static/sass/course/base/_extends.scss
index 78618df83d..c072ea3b1f 100644
--- a/lms/static/sass/course/base/_extends.scss
+++ b/lms/static/sass/course/base/_extends.scss
@@ -6,28 +6,34 @@ h1.top-header {
padding-bottom: lh();
}
-.light-button, a.light-button {
- border: 1px solid #ccc;
- @include border-radius(3px);
- @include box-shadow(inset 0 1px 0 #fff);
- color: #666;
- cursor: pointer;
- font: 400 $body-font-size $body-font-family;
- @include linear-gradient(#fff, lighten(#888, 40%));
- padding: 4px 8px;
- text-decoration: none;
- text-shadow: none;
+.button-reset {
text-transform: none;
letter-spacing: 0;
- -webkit-font-smoothing: antialiased;
- &:hover, &:focus {
- border: 1px solid #ccc;
- @include linear-gradient(#fff, lighten(#888, 37%));
+ &:hover {
text-decoration: none;
}
}
+.light-button, a.light-button, // only used in askbot as classes
+.gray-button {
+ @include button(simple, #eee);
+ @extend .button-reset;
+ font-size: em(13);
+}
+
+.blue-button {
+ @include button(simple, $blue);
+ @extend .button-reset;
+ font-size: em(13);
+}
+
+.pink-button {
+ @include button(simple, $pink);
+ @extend .button-reset;
+ font-size: em(13);
+}
+
.content {
@include box-sizing(border-box);
display: table-cell;
diff --git a/lms/static/sass/course/courseware/_courseware.scss b/lms/static/sass/course/courseware/_courseware.scss
index 97bf10b151..24ce46a9a3 100644
--- a/lms/static/sass/course/courseware/_courseware.scss
+++ b/lms/static/sass/course/courseware/_courseware.scss
@@ -159,7 +159,7 @@ div.course-wrapper {
a.ui-slider-handle {
@include box-shadow(inset 0 1px 0 lighten($pink, 10%));
- background: $mit-red url(../images/slider-bars.png) center center no-repeat;
+ background: $pink url(../images/slider-bars.png) center center no-repeat;
border: 1px solid darken($pink, 20%);
cursor: pointer;
diff --git a/lms/static/sass/course/discussion/_answers.scss b/lms/static/sass/course/discussion/_answers.scss
index 73660dd336..c4aa683726 100644
--- a/lms/static/sass/course/discussion/_answers.scss
+++ b/lms/static/sass/course/discussion/_answers.scss
@@ -91,7 +91,7 @@ div.answer-block {
div.deleted {
p {
- color: $mit-red;
+ color: $pink;
}
}
@@ -113,7 +113,7 @@ div.paginator {
&.curr {
background: none;
- color: $mit-red;
+ color: $pink;
font-weight: bold;
}
diff --git a/lms/static/sass/course/discussion/_discussion.scss b/lms/static/sass/course/discussion/_discussion.scss
index e12f308ae2..3aaf73c4ac 100644
--- a/lms/static/sass/course/discussion/_discussion.scss
+++ b/lms/static/sass/course/discussion/_discussion.scss
@@ -76,6 +76,6 @@ body.askbot {
}
.acSelect {
- background-color: $mit-red;
+ background-color: $pink;
color: #fff;
}
diff --git a/lms/static/sass/course/discussion/_forms.scss b/lms/static/sass/course/discussion/_forms.scss
index 43ba07df19..2f3bd86962 100644
--- a/lms/static/sass/course/discussion/_forms.scss
+++ b/lms/static/sass/course/discussion/_forms.scss
@@ -16,6 +16,18 @@ form.answer-form {
margin-top: 15px;
resize: vertical;
width: 99%;
+
+ editor {
+ min-height: em(120);
+ }
+ }
+
+ div.checkbox {
+ margin-bottom: lh();
+
+ label {
+ display: inline;
+ }
}
div.form-item {
@@ -97,7 +109,7 @@ form.answer-form {
margin-left: 2.5%;
padding-left: 1.5%;
border-left: 1px dashed #ddd;
- color: $mit-red;
+ color: $pink;
}
ul, ol, pre {
@@ -141,32 +153,32 @@ form.question-form {
}
div#question-list {
- background-color: rgba(255,255,255,0.95);
- @include box-sizing(border-box);
- margin-top: -15px;
- max-width: 505px;
- min-width: 300px;
- overflow: hidden;
- padding-left: 5px;
- position: absolute;
- width: 35%;
- z-index: 9999;
+ background-color: rgba(255,255,255,0.95);
+ @include box-sizing(border-box);
+ margin-top: -15px;
+ max-width: 505px;
+ min-width: 300px;
+ overflow: hidden;
+ padding-left: 5px;
+ position: absolute;
+ width: 35%;
+ z-index: 9999;
- h2 {
- text-transform: none;
- padding: 8px 0;
- border-bottom: 1px solid #eee;
- margin: 0;
+ h2 {
+ text-transform: none;
+ padding: 8px 0;
+ border-bottom: 1px solid #eee;
+ margin: 0;
- span {
- background: #eee;
- color: #555;
- padding: 2px 5px;
- @include border-radius(2px);
- margin-right: 5px;
+ span {
+ background: #eee;
+ color: #555;
+ padding: 2px 5px;
+ @include border-radius(2px);
+ margin-right: 5px;
+ }
}
}
}
-}
diff --git a/lms/static/sass/course/discussion/_modals.scss b/lms/static/sass/course/discussion/_modals.scss
index f1d1fd78cf..6c91956fc9 100644
--- a/lms/static/sass/course/discussion/_modals.scss
+++ b/lms/static/sass/course/discussion/_modals.scss
@@ -1,6 +1,6 @@
// Style for modal boxes that pop up to notify the user of various events
.vote-notification {
- background-color: darken($mit-red, 7%);
+ background-color: darken(#666, 7%);
@include border-radius(4px);
@include box-shadow(0px 2px 9px #aaa);
color: white;
@@ -14,12 +14,12 @@
z-index: 1;
h3 {
- background: $mit-red;
+ background: #666;
padding: 10px 10px 10px 10px;
font-size: 13px;
margin-bottom: 5px;
- border-bottom: darken(#8e0000, 10%) 1px solid;
- @include box-shadow(0 1px 0 lighten($mit-red, 10%));
+ border-bottom: darken(#666, 10%) 1px solid;
+ @include box-shadow(0 1px 0 lighten(#666, 10%));
color: #fff;
font-weight: normal;
@include border-radius(4px 4px 0 0);
diff --git a/lms/static/sass/course/discussion/_question-view.scss b/lms/static/sass/course/discussion/_question-view.scss
index f7657dbf97..878fb8bede 100644
--- a/lms/static/sass/course/discussion/_question-view.scss
+++ b/lms/static/sass/course/discussion/_question-view.scss
@@ -4,7 +4,7 @@ div.question-header {
@include clearfix();
div.official-stamp {
- background: $mit-red;
+ background: $pink;
color: #fff;
font-size: 12px;
margin-left: -1px;
@@ -120,7 +120,7 @@ div.question-header {
margin-left: 2.5%;
padding-left: 1.5%;
border-left: 1px dashed #ddd;
- color: $mit-red;;
+ color: $pink;
}
ul, ol, pre {
@@ -217,13 +217,13 @@ div.question-header {
form.post-comments {
padding: 15px;
- button {
- color: #fff;
+ button:first-of-type {
+ @extend .blue-button;
}
button:last-child {
margin-left: 10px;
- @extend .light-button;
+ float: right;
}
}
@@ -352,7 +352,7 @@ div.question-header {
}
div.question-status {
- background: $mit-red;
+ background: $pink;
clear:both;
color: #fff;
display: block;
diff --git a/lms/static/sass/course/discussion/_sidebar.scss b/lms/static/sass/course/discussion/_sidebar.scss
index 59dcfaf449..b248fb18d7 100644
--- a/lms/static/sass/course/discussion/_sidebar.scss
+++ b/lms/static/sass/course/discussion/_sidebar.scss
@@ -43,8 +43,8 @@ div.discussion-wrapper aside {
width: 27%;
float: right;
text-align: center;
- padding-left: 0;
- padding-right: 0;
+ padding: 4px 0;
+ text-transform: capitalize;
}
input[type="text"] {
@@ -300,7 +300,7 @@ div.discussion-wrapper aside {
border-top: 0;
a {
- @extend .light-button;
+ @extend .gray-button;
@include box-sizing(border-box);
display: block;
text-align: center;
diff --git a/lms/templates/course_navigation.html b/lms/templates/courseware/course_navigation.html
similarity index 94%
rename from lms/templates/course_navigation.html
rename to lms/templates/courseware/course_navigation.html
index 3945572e12..747472ba31 100644
--- a/lms/templates/course_navigation.html
+++ b/lms/templates/courseware/course_navigation.html
@@ -38,7 +38,7 @@ def url_class(url):
Wiki
% endif
% if user.is_authenticated():
- Profile
+ Progress
% endif
% if staff_access:
Instructor
diff --git a/lms/templates/courseware-error.html b/lms/templates/courseware/courseware-error.html
similarity index 67%
rename from lms/templates/courseware-error.html
rename to lms/templates/courseware/courseware-error.html
index 3c26ae8aeb..169c7de8bc 100644
--- a/lms/templates/courseware-error.html
+++ b/lms/templates/courseware/courseware-error.html
@@ -1,8 +1,13 @@
-<%inherit file="main.html" />
+<%inherit file="/main.html" />
+<%namespace name='static' file='../static_content.html'/>
<%block name="bodyclass">courseware%block>
<%block name="title">Courseware – edX %block>
-<%include file="course_navigation.html" args="active_page='courseware'" />
+<%block name="headextra">
+ <%static:css group='course'/>
+%block>
+
+<%include file="/courseware/course_navigation.html" args="active_page='courseware'" />
diff --git a/lms/templates/courseware.html b/lms/templates/courseware/courseware.html
similarity index 87%
rename from lms/templates/courseware.html
rename to lms/templates/courseware/courseware.html
index 6c148153ad..6102afc6cb 100644
--- a/lms/templates/courseware.html
+++ b/lms/templates/courseware/courseware.html
@@ -1,7 +1,7 @@
-<%inherit file="main.html" />
-<%namespace name='static' file='static_content.html'/>
+<%inherit file="/main.html" />
+<%namespace name='static' file='/static_content.html'/>
<%block name="bodyclass">courseware%block>
-<%block name="title">Courseware – MITx 6.002x %block>
+<%block name="title">${course.number} Courseware %block>
<%block name="headextra">
<%static:css group='course'/>
@@ -22,7 +22,7 @@
<%static:js group='courseware'/>
<%include file="discussion/_js_dependencies.html" />
-
+ <%include file="/mathjax_include.html" />
%block>
-<%include file="course_navigation.html" args="active_page='courseware'" />
+<%include file="/courseware/course_navigation.html" args="active_page='courseware'" />
diff --git a/lms/templates/grade_summary.html b/lms/templates/courseware/grade_summary.html
similarity index 61%
rename from lms/templates/grade_summary.html
rename to lms/templates/courseware/grade_summary.html
index 3fdcd910ae..e5f91d9ddf 100644
--- a/lms/templates/grade_summary.html
+++ b/lms/templates/courseware/grade_summary.html
@@ -1,8 +1,8 @@
-<%inherit file="main.html" />
+<%inherit file="/main.html" />
<%! from django.core.urlresolvers import reverse %>
-<%namespace name='static' file='static_content.html'/>
+<%namespace name='static' file='/static_content.html'/>
-<%include file="course_navigation.html" args="active_page=''" />
+<%include file="/courseware/course_navigation.html" args="active_page=''" />
diff --git a/lms/templates/gradebook.html b/lms/templates/courseware/gradebook.html
similarity index 90%
rename from lms/templates/gradebook.html
rename to lms/templates/courseware/gradebook.html
index 787fc23c9a..20426041c4 100644
--- a/lms/templates/gradebook.html
+++ b/lms/templates/courseware/gradebook.html
@@ -1,6 +1,6 @@
-<%inherit file="main.html" />
+<%inherit file="/main.html" />
<%! from django.core.urlresolvers import reverse %>
-<%namespace name='static' file='static_content.html'/>
+<%namespace name='static' file='/static_content.html'/>
<%block name="js_extra">
@@ -28,7 +28,7 @@
%block>
-<%include file="course_navigation.html" args="active_page=''" />
+<%include file="/courseware/course_navigation.html" args="active_page=''" />
@@ -49,7 +49,7 @@
%for student in students:
- ${student['username']}
+ ${student['username']}
%endfor
diff --git a/lms/templates/info.html b/lms/templates/courseware/info.html
similarity index 83%
rename from lms/templates/info.html
rename to lms/templates/courseware/info.html
index a04e31896f..33195f985e 100644
--- a/lms/templates/info.html
+++ b/lms/templates/courseware/info.html
@@ -1,11 +1,13 @@
-<%inherit file="main.html" />
-<%namespace name='static' file='static_content.html'/>
+<%inherit file="/main.html" />
+<%namespace name='static' file='/static_content.html'/>
<%block name="headextra">
<%static:css group='course'/>
%block>
-<%include file="course_navigation.html" args="active_page='info'" />
+<%block name="title">
${course.number} Course Info %block>
+
+<%include file="/courseware/course_navigation.html" args="active_page='info'" />
<%!
from courseware.courses import get_course_info_section
%>
diff --git a/lms/templates/instructor_dashboard.html b/lms/templates/courseware/instructor_dashboard.html
similarity index 74%
rename from lms/templates/instructor_dashboard.html
rename to lms/templates/courseware/instructor_dashboard.html
index 6b87f63031..9508624f9b 100644
--- a/lms/templates/instructor_dashboard.html
+++ b/lms/templates/courseware/instructor_dashboard.html
@@ -1,12 +1,12 @@
-<%inherit file="main.html" />
+<%inherit file="/main.html" />
<%! from django.core.urlresolvers import reverse %>
-<%namespace name='static' file='static_content.html'/>
+<%namespace name='static' file='/static_content.html'/>
<%block name="headextra">
<%static:css group='course'/>
%block>
-<%include file="course_navigation.html" args="active_page='instructor'" />
+<%include file="/courseware/course_navigation.html" args="active_page='instructor'" />
diff --git a/lms/templates/courseware/progress.html b/lms/templates/courseware/progress.html
new file mode 100644
index 0000000000..86649a2e91
--- /dev/null
+++ b/lms/templates/courseware/progress.html
@@ -0,0 +1,85 @@
+<%inherit file="/main.html" />
+<%namespace name='static' file='/static_content.html'/>
+
+<%block name="headextra">
+ <%static:css group='course'/>
+%block>
+
+<%namespace name="progress_graph" file="/courseware/progress_graph.js"/>
+
+<%block name="title">
${course.number} Progress %block>
+
+<%!
+ from django.core.urlresolvers import reverse
+%>
+
+<%block name="js_extra">
+
+
+
+
+%block>
+
+<%include file="/courseware/course_navigation.html" args="active_page='progress'" />
+
+
+
+
+
+
+
+
+
+
+ %for chapter in courseware_summary:
+ %if not chapter['display_name'] == "hidden":
+
+ ${ chapter['display_name'] }
+
+
+ %for section in chapter['sections']:
+
+ <%
+ earned = section['section_total'].earned
+ total = section['section_total'].possible
+ percentageString = "{0:.0%}".format( float(earned)/total) if earned > 0 and total > 0 else ""
+ %>
+
+
+ ${ section['display_name'] } ${"({0:.3n}/{1:.3n}) {2}".format( float(earned), float(total), percentageString )}
+
+ ${section['format']}
+
+ %if 'due' in section and section['due']!="":
+
+ due ${section['due']}
+
+ %endif
+
+
+ %if len(section['scores']) > 0:
+
+ ${ "Problem Scores: " if section['graded'] else "Practice Scores: "}
+
+ %for score in section['scores']:
+ ${"{0:.3n}/{1:.3n}".format(float(score.earned),float(score.possible))}
+ %endfor
+
+
+ %endif
+
+
+ %endfor
+
+
+ %endif
+ %endfor
+
+
+
+
+
diff --git a/lms/templates/profile_graphs.js b/lms/templates/courseware/progress_graph.js
similarity index 100%
rename from lms/templates/profile_graphs.js
rename to lms/templates/courseware/progress_graph.js
diff --git a/lms/templates/discussion/index.html b/lms/templates/discussion/index.html
index f34152c569..9b0494dfff 100644
--- a/lms/templates/discussion/index.html
+++ b/lms/templates/discussion/index.html
@@ -11,7 +11,7 @@
<%include file="_js_dependencies.html" />
%block>
-<%include file="../course_navigation.html" args="active_page='discussion'" />
+<%include file="/courseware/course_navigation.html" args="active_page='discussion'" />
diff --git a/lms/templates/discussion/user_profile.html b/lms/templates/discussion/user_profile.html
index 3825879c3b..c4d0d85607 100644
--- a/lms/templates/discussion/user_profile.html
+++ b/lms/templates/discussion/user_profile.html
@@ -14,7 +14,7 @@
<%include file="_js_dependencies.html" />
%block>
-<%include file="../course_navigation.html" args="active_page='discussion'" />
+<%include file="/courseware/course_navigation.html" args="active_page='discussion'" />
diff --git a/lms/templates/gradebook_profilegraphs.html b/lms/templates/gradebook_profilegraphs.html
deleted file mode 100644
index fc36c84fb8..0000000000
--- a/lms/templates/gradebook_profilegraphs.html
+++ /dev/null
@@ -1,31 +0,0 @@
-<%inherit file="main.html" />
-<%namespace name='static' file='static_content.html'/>
-<%namespace name="profile_graphs" file="profile_graphs.js"/>
-
-<%block name="js_extra">
-
-
-
- % for s in students:
-
- %endfor
-%block>
-
-<%include file="navigation.html" args="active_page=''" />
-
-
-
- Gradebook
-
- % for s in students:
-
-
-
-
- % endfor
-
-
-
-
diff --git a/lms/templates/news.html b/lms/templates/news.html
index 63724759a5..2c37975e2a 100644
--- a/lms/templates/news.html
+++ b/lms/templates/news.html
@@ -10,7 +10,7 @@
<%block name="js_extra">
%block>
-<%include file="course_navigation.html" args="active_page='news'" />
+<%include file="/courseware/course_navigation.html" args="active_page='news'" />
diff --git a/lms/templates/profile.html b/lms/templates/profile.html
deleted file mode 100644
index ca27920a1b..0000000000
--- a/lms/templates/profile.html
+++ /dev/null
@@ -1,293 +0,0 @@
-<%inherit file="main.html" />
-<%namespace name='static' file='static_content.html'/>
-
-<%block name="headextra">
- <%static:css group='course'/>
-%block>
-
-<%namespace name="profile_graphs" file="profile_graphs.js"/>
-
-<%block name="title">
Profile - edX 6.002x %block>
-
-<%!
- from django.core.urlresolvers import reverse
-%>
-
-<%block name="js_extra">
-
-
-
-
-
-
-%block>
-
-<%include file="course_navigation.html" args="active_page='profile'" />
-
-
-
-
-
-
-
-
-
-
- %for chapter in courseware_summary:
- %if not chapter['display_name'] == "hidden":
-
- ${ chapter['display_name'] }
-
-
- %for section in chapter['sections']:
-
- <%
- earned = section['section_total'].earned
- total = section['section_total'].possible
- percentageString = "{0:.0%}".format( float(earned)/total) if earned > 0 and total > 0 else ""
- %>
-
-
- ${ section['display_name'] } ${"({0:.3n}/{1:.3n}) {2}".format( float(earned), float(total), percentageString )}
-
- ${section['format']}
-
- %if 'due' in section and section['due']!="":
-
- due ${section['due']}
-
- %endif
-
-
- %if len(section['scores']) > 0:
-
- ${ "Problem Scores: " if section['graded'] else "Practice Scores: "}
-
- %for score in section['scores']:
- ${"{0:.3n}/{1:.3n}".format(float(score.earned),float(score.possible))}
- %endfor
-
-
- %endif
-
-
- %endfor
-
-
- %endif
- %endfor
-
-
-
-
-
-
-
-
-
-
-
Password Reset Email Sent
-
- An email has been sent to ${email}. Follow the link in the email to change your password.
-
-
-
-
-
-
- Apply to change your name
-
-
-
-
-
-
-
-
-
-
-
- Deactivate edX Account
-
-
-
Once you deactivate you’re MITx account you will no longer recieve updates and new class announcements from MITx .
-
If you’d like to still get updates and new class announcements you can just unenroll and keep your account active.
-
-
-
-
-
-
-
-
-
-
-
-
-
- Unenroll from 6.002x
-
-
-
Please note: you will still receive updates and new class announcements from edX . If you don’t wish to receive any more updates or announcements deactivate your account .
-
-
-
-
-
-
-
-
-
diff --git a/lms/templates/simplewiki/simplewiki_base.html b/lms/templates/simplewiki/simplewiki_base.html
index 8736f3e421..e19d8d61ca 100644
--- a/lms/templates/simplewiki/simplewiki_base.html
+++ b/lms/templates/simplewiki/simplewiki_base.html
@@ -79,7 +79,7 @@
<%block name="bodyextra">
%if course:
-<%include file="../course_navigation.html" args="active_page='wiki'" />
+<%include file="/courseware/course_navigation.html" args="active_page='wiki'" />
%endif
diff --git a/lms/templates/staticbook.html b/lms/templates/staticbook.html
index 2a0a6f03bb..a8e057578b 100644
--- a/lms/templates/staticbook.html
+++ b/lms/templates/staticbook.html
@@ -32,7 +32,7 @@ function goto_page(n) {
if(n<10) {
prefix="00";
}
- $("#bookpage").attr("src","${ settings.BOOK_URL }p"+prefix+n+".png");
+ $("#bookpage").attr("src","${ book_url }p"+prefix+n+".png");
$.cookie("book_page", n, {'expires':3650, 'path':'/'});
};
@@ -61,7 +61,7 @@ $("#open_close_accordion a").click(function(){
%block>
-<%include file="course_navigation.html" args="active_page='book'" />
+<%include file="/courseware/course_navigation.html" args="active_page='book'" />
@@ -113,7 +113,7 @@ $("#open_close_accordion a").click(function(){
-
+
diff --git a/lms/templates/wiki/base.html b/lms/templates/wiki/base.html
index 2c1943e2a5..d4428da466 100644
--- a/lms/templates/wiki/base.html
+++ b/lms/templates/wiki/base.html
@@ -47,7 +47,7 @@
{% block body %}
{% if course %}
- {% include "course_navigation.html" with active_page_context="wiki" %}
+ {% include "courseware/course_navigation.html" with active_page_context="wiki" %}
{% endif %}
diff --git a/lms/urls.py b/lms/urls.py
index 15943a1350..a4626896d2 100644
--- a/lms/urls.py
+++ b/lms/urls.py
@@ -100,7 +100,7 @@ if settings.COURSEWARE_ENABLED:
url(r'^masquerade/', include('masquerade.urls')),
url(r'^jump_to/(?P.*)$', 'courseware.views.jump_to', name="jump_to"),
- url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/modx/(?P.*?)/(?P[^/]*)$',
+ url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/modx/(?P.*?)/(?P[^/]*)$',
'courseware.module_render.modx_dispatch',
name='modx_dispatch'),
url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/xqueue/(?P[^/]*)/(?P.*?)/(?P[^/]*)$',
@@ -138,11 +138,11 @@ if settings.COURSEWARE_ENABLED:
'courseware.views.index', name="courseware_chapter"),
url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/courseware/(?P[^/]*)/(?P[^/]*)/$',
'courseware.views.index', name="courseware_section"),
- url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/profile$',
- 'courseware.views.profile', name="profile"),
+ url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/progress$',
+ 'courseware.views.progress', name="progress"),
# Takes optional student_id for instructor use--shows profile as that student sees it.
- url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/profile/(?P[^/]*)/$',
- 'courseware.views.profile', name="student_profile"),
+ url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/progress/(?P[^/]*)/$',
+ 'courseware.views.progress', name="student_progress"),
# For the instructor
url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/instructor$',
@@ -169,18 +169,18 @@ if settings.COURSEWARE_ENABLED:
if settings.WIKI_ENABLED:
from wiki.urls import get_pattern as wiki_pattern
from django_notify.urls import get_pattern as notify_pattern
-
+
# Note that some of these urls are repeated in course_wiki.course_nav. Make sure to update
# them together.
- urlpatterns += (
+ urlpatterns += (
# First we include views from course_wiki that we use to override the default views.
# They come first in the urlpatterns so they get resolved first
url('^wiki/create-root/$', 'course_wiki.views.root_create', name='root_create'),
-
+
url(r'^wiki/', include(wiki_pattern())),
url(r'^notify/', include(notify_pattern())),
-
+
# These urls are for viewing the wiki in the context of a course. They should
# never be returned by a reverse() so they come after the other url patterns
url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/course_wiki/?$',
diff --git a/repo-requirements.txt b/repo-requirements.txt
index dced5b960b..0cb7f2d4f5 100644
--- a/repo-requirements.txt
+++ b/repo-requirements.txt
@@ -1,2 +1,6 @@
+-e git://github.com/MITx/django-staticfiles.git@6d2504e5c8#egg=django-staticfiles
+-e git://github.com/MITx/django-pipeline.git#egg=django-pipeline
+-e git://github.com/benjaoming/django-wiki.git@c145596#egg=django-wiki
+-e git://github.com/dementrock/pystache_custom.git#egg=pystache_custom
-e common/lib/capa
-e common/lib/xmodule
diff --git a/requirements.txt b/requirements.txt
index f560110bcc..7e69577073 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -11,8 +11,6 @@ python-memcached
python-openid
path.py
django_debug_toolbar
--e git://github.com/MITx/django-pipeline.git#egg=django-pipeline
-django-staticfiles>=1.2.1
fs
beautifulsoup
beautifulsoup4
@@ -44,6 +42,5 @@ django-ses
django-storages
django-threaded-multihost
django-sekizai<0.7
--e git://github.com/benjaoming/django-wiki.git@c145596#egg=django-wiki
--e git://github.com/dementrock/pystache_custom.git#egg=pystache_custom
+networkx
-r repo-requirements.txt