Ready to implement path_to_location
* Clean up test data for simple, toy courses * clean up test_mongo.py * write initial test for path_to_location * hook up view to use path_to_location Next: actually implement it :)
This commit is contained in:
@@ -253,3 +253,26 @@ class ModuleStore(object):
|
||||
in this modulestore.
|
||||
'''
|
||||
raise NotImplementedError
|
||||
|
||||
def path_to_location(self, location, course=None, chapter=None, section=None):
|
||||
'''
|
||||
Try to find a course/chapter/section[/position] path to this location.
|
||||
|
||||
raise ItemNotFoundError if the location doesn't exist.
|
||||
|
||||
If course, chapter, section are not None, restrict search to paths with those
|
||||
components as specified.
|
||||
|
||||
raise NoPathToItem if the location exists, but isn't accessible via
|
||||
a path that matches the course/chapter/section restrictions.
|
||||
|
||||
In general, a location may be accessible via many paths. This method may
|
||||
return any valid path.
|
||||
|
||||
Return a tuple (course, chapter, section, position).
|
||||
|
||||
If the section a sequence, position should be the position of this location
|
||||
in that sequence. Otherwise, position should be None.
|
||||
'''
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@@ -13,3 +13,6 @@ class InsufficientSpecificationError(Exception):
|
||||
|
||||
class InvalidLocationError(Exception):
|
||||
pass
|
||||
|
||||
class NoPathToItem(Exception):
|
||||
pass
|
||||
|
||||
@@ -251,7 +251,7 @@ class MongoModuleStore(ModuleStore):
|
||||
|
||||
def update_metadata(self, location, metadata):
|
||||
"""
|
||||
Set the children for the item specified by the location to
|
||||
Set the metadata for the item specified by the location to
|
||||
metadata
|
||||
|
||||
location: Something that can be passed to Location
|
||||
@@ -264,3 +264,25 @@ class MongoModuleStore(ModuleStore):
|
||||
{'_id': Location(location).dict()},
|
||||
{'$set': {'metadata': metadata}}
|
||||
)
|
||||
|
||||
def path_to_location(self, location, course=None):
|
||||
'''
|
||||
Try to find a course/chapter/section[/position] path to this location.
|
||||
|
||||
raise ItemNotFoundError if the location doesn't exist.
|
||||
|
||||
If course is not None, restrict search to paths in that course.
|
||||
|
||||
raise NoPathToItem if the location exists, but isn't accessible via
|
||||
a chapter/section path in the course(s) being searched.
|
||||
|
||||
In general, a location may be accessible via many paths. This method may
|
||||
return any valid path.
|
||||
|
||||
Return a tuple (course, chapter, section, position).
|
||||
|
||||
If the section a sequence, position should be the position of this location
|
||||
in that sequence. Otherwise, position should be None.
|
||||
'''
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from nose.tools import assert_equals, assert_raises, assert_not_equals, with_set
|
||||
from path import path
|
||||
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.exceptions import InvalidLocationError
|
||||
from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundError
|
||||
from xmodule.modulestore.mongo import MongoModuleStore
|
||||
from xmodule.modulestore.xml_importer import import_from_xml
|
||||
|
||||
@@ -26,36 +26,60 @@ FS_ROOT = DATA_DIR # TODO (vshnayder): will need a real fs_root for testing loa
|
||||
DEFAULT_CLASS = 'xmodule.raw_module.RawDescriptor'
|
||||
|
||||
|
||||
connection = None
|
||||
class TestMongoModuleStore(object):
|
||||
|
||||
def setup():
|
||||
global connection
|
||||
connection = pymongo.connection.Connection(HOST, PORT)
|
||||
@classmethod
|
||||
def setupClass(cls):
|
||||
cls.connection = pymongo.connection.Connection(HOST, PORT)
|
||||
cls.connection.drop_database(DB)
|
||||
|
||||
@classmethod
|
||||
def teardownClass(cls):
|
||||
pass
|
||||
|
||||
def setUp(self):
|
||||
# connect to the db
|
||||
self.store = MongoModuleStore(HOST, DB, COLLECTION, FS_ROOT, default_class=DEFAULT_CLASS)
|
||||
# Explicitly list the courses to load (don't want the big one)
|
||||
courses = ['toy', 'simple']
|
||||
import_from_xml(self.store, DATA_DIR, courses)
|
||||
self.connection = TestMongoModuleStore.connection
|
||||
|
||||
def tearDown(self):
|
||||
# Destroy the test db.
|
||||
self.connection.drop_database(DB)
|
||||
self.store = None
|
||||
|
||||
|
||||
def setup_func():
|
||||
# connect to the db
|
||||
global store
|
||||
store = MongoModuleStore(HOST, DB, COLLECTION, FS_ROOT, default_class=DEFAULT_CLASS)
|
||||
print 'data_dir: {0}'.format(DATA_DIR)
|
||||
import_from_xml(store, DATA_DIR)
|
||||
|
||||
def teardown_func():
|
||||
global store
|
||||
store = None
|
||||
# Destroy the test db.
|
||||
connection.drop_database(DB)
|
||||
|
||||
|
||||
@with_setup(setup_func, teardown_func)
|
||||
def test_init():
|
||||
'''Just make sure the db loads'''
|
||||
pass
|
||||
|
||||
@with_setup(setup_func, teardown_func)
|
||||
def test_get_courses():
|
||||
'''Make sure the course objects loaded properly'''
|
||||
courses = store.get_courses()
|
||||
print courses
|
||||
def test_init(self):
|
||||
'''Just make sure the db loads'''
|
||||
ids = list(self.connection[DB][COLLECTION].find({}, {'_id': True}))
|
||||
print len(ids)
|
||||
|
||||
def test_get_courses(self):
|
||||
'''Make sure the course objects loaded properly'''
|
||||
courses = self.store.get_courses()
|
||||
assert_equals(len(courses), 2)
|
||||
courses.sort(key=lambda c: c.id)
|
||||
assert_equals(courses[0].id, 'edX/simple/2012_Fall')
|
||||
assert_equals(courses[1].id, 'edX/toy/2012_Fall')
|
||||
|
||||
def Xtest_path_to_location(self):
|
||||
'''Make sure that path_to_location works'''
|
||||
should_work = (
|
||||
("i4x://edX/toy/video/Welcome", ("toy", "Overview", None, None)),
|
||||
)
|
||||
for location, expected in should_work:
|
||||
assert_equals(self.store.path_to_location(location), expected)
|
||||
|
||||
not_found = (
|
||||
"i4x://edX/toy/video/WelcomeX",
|
||||
)
|
||||
for location in not_found:
|
||||
assert_raises(ItemNotFoundError, self.store.path_to_location, location)
|
||||
|
||||
no_path = (
|
||||
"i4x://edX/toy/video/Lost_Video",
|
||||
)
|
||||
for location in not_found:
|
||||
assert_raises(ItemNotFoundError, self.store.path_to_location, location)
|
||||
|
||||
|
||||
24
common/test/data/simple/course.xml
Normal file
24
common/test/data/simple/course.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<course name="A Simple Course" org="edX" course="simple" graceperiod="1 day 5 hours 59 minutes 59 seconds" slug="2012_Fall">
|
||||
<chapter name="Overview">
|
||||
<video name="Welcome" youtube="0.75:izygArpw-Qo,1.0:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8"/>
|
||||
<videosequence format="Lecture Sequence" name="A simple sequence">
|
||||
<html id="toylab" filename="toylab"/>
|
||||
<video name="S0V1: Video Resources" youtube="0.75:EuzkdzfR0i8,1.0:1bK-WdDi6Qw,1.25:0v1VzoDVUTM,1.50:Bxk_-ZJb240"/>
|
||||
</videosequence>
|
||||
<section name="Lecture 2">
|
||||
<sequential>
|
||||
<video youtube="1.0:TBvX7HzxexQ"/>
|
||||
<problem name="L1 Problem 1" points="1" type="lecture" showanswer="attempted" filename="L1_Problem_1" rerandomize="never"/>
|
||||
</sequential>
|
||||
</section>
|
||||
</chapter>
|
||||
<chapter name="Chapter 2">
|
||||
<section name="Problem Set 1">
|
||||
<sequential>
|
||||
<problem type="lecture" showanswer="attempted" rerandomize="true" title="A simple coding problem" name="Simple coding problem" filename="ps01-simple"/>
|
||||
</sequential>
|
||||
</section>
|
||||
</chapter>
|
||||
<video name="Lost Video" youtube="1.0:TBvX7HzxexQ"/>
|
||||
|
||||
</course>
|
||||
3
common/test/data/simple/html/toylab.html
Normal file
3
common/test/data/simple/html/toylab.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<b>Lab 2A: Superposition Experiment</b>
|
||||
|
||||
<p>Isn't the toy course great?</p>
|
||||
43
common/test/data/simple/problems/L1_Problem_1.xml
Normal file
43
common/test/data/simple/problems/L1_Problem_1.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0"?>
|
||||
<problem>
|
||||
<p>
|
||||
<h1>Finger Exercise 1</h1>
|
||||
</p>
|
||||
<p>
|
||||
Here are two definitions: </p>
|
||||
<ol class="enumerate">
|
||||
<li>
|
||||
<p>
|
||||
Declarative knowledge refers to statements of fact. </p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
Imperative knowledge refers to 'how to' methods. </p>
|
||||
</li>
|
||||
</ol>
|
||||
<p>
|
||||
Which of the following choices is correct? </p>
|
||||
<ol class="enumerate">
|
||||
<li>
|
||||
<p>
|
||||
Statement 1 is true, Statement 2 is false </p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
Statement 1 is false, Statement 2 is true </p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
Statement 1 and Statement 2 are both false </p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
Statement 1 and Statement 2 are both true </p>
|
||||
</li>
|
||||
</ol>
|
||||
<p>
|
||||
<symbolicresponse answer="4">
|
||||
<textline size="90" math="1"/>
|
||||
</symbolicresponse>
|
||||
</p>
|
||||
</problem>
|
||||
62
common/test/data/simple/problems/ps01-simple.xml
Normal file
62
common/test/data/simple/problems/ps01-simple.xml
Normal file
@@ -0,0 +1,62 @@
|
||||
<problem><style media="all" type="text/css"/>
|
||||
<text><h2>Paying Off Credit Card Debt</h2>
|
||||
<p> Each month, a credit
|
||||
card statement will come with the option for you to pay a
|
||||
minimum amount of your charge, usually 2% of the balance due.
|
||||
However, the credit card company earns money by charging
|
||||
interest on the balance that you don't pay. So even if you
|
||||
pay credit card payments on time, interest is still accruing
|
||||
on the outstanding balance.</p>
|
||||
<p >Say you've made a
|
||||
$5,000 purchase on a credit card with 18% annual interest
|
||||
rate and 2% minimum monthly payment rate. After a year, how
|
||||
much is the remaining balance? Use the following
|
||||
equations.</p>
|
||||
<blockquote>
|
||||
<p><strong>Minimum monthly payment</strong>
|
||||
= (Minimum monthly payment rate) x (Balance)<br/>
|
||||
(Minimum monthly payment gets split into interest paid and
|
||||
principal paid)<br/>
|
||||
<strong>Interest Paid</strong> = (Annual interest rate) / (12
|
||||
months) x (Balance)<br/>
|
||||
<strong>Principal paid</strong> = (Minimum monthly payment) -
|
||||
(Interest paid)<br/>
|
||||
<strong>Remaining balance</strong> = Balance - (Principal
|
||||
paid)</p>
|
||||
</blockquote>
|
||||
<p >For month 1, compute the minimum monthly payment by taking 2% of the balance.</p>
|
||||
<blockquote xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:x="urn:schemas-microsoft-com:office:excel">
|
||||
<p><strong>Minimum monthly payment</strong>
|
||||
= .02 x $5000 = $100</p>
|
||||
<p>We can't simply deduct this from the balance because
|
||||
there is compounding interest. Of this $100 monthly
|
||||
payment, compute how much will go to paying off interest
|
||||
and how much will go to paying off the principal. Remember
|
||||
that it's the annual interest rate that is given, so we
|
||||
need to divide it by 12 to get the monthly interest
|
||||
rate.</p>
|
||||
<p><strong>Interest paid</strong> = .18/12 x $5000 =
|
||||
$75<br/>
|
||||
<strong>Principal paid</strong> = $100 - $75 = $25</p>
|
||||
<p>The remaining balance at the end of the first month will
|
||||
be the principal paid this month subtracted from the
|
||||
balance at the start of the month.</p>
|
||||
<p><strong>Remaining balance</strong> = $5000 - $25 =
|
||||
$4975</p>
|
||||
</blockquote>
|
||||
<p xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:x="urn:schemas-microsoft-com:office:excel">For month 2, we
|
||||
repeat the same steps.</p>
|
||||
<blockquote xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:x="urn:schemas-microsoft-com:office:excel">
|
||||
<p><strong>Minimum monthly payment</strong>
|
||||
= .02 x $4975 = $99.50<br/>
|
||||
<strong>Interest Paid</strong> = .18/12 x $4975 =
|
||||
$74.63<br/>
|
||||
<strong>Principal Paid</strong> = $99.50 - $74.63 =
|
||||
$24.87<br/>
|
||||
<strong>Remaining Balance</strong> = $4975 - $24.87 =
|
||||
$4950.13</p>
|
||||
</blockquote>
|
||||
<p xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:x="urn:schemas-microsoft-com:office:excel">After 12 months, the
|
||||
total amount paid is $1167.55, leaving an outstanding balance
|
||||
of $4708.10. Pretty depressing!</p>
|
||||
</text></problem>
|
||||
@@ -1,9 +1,9 @@
|
||||
<course name="Toy Course" org="edX" course="toy" graceperiod="1 day 5 hours 59 minutes 59 seconds" slug="2012_Fall" start="2015-07-17T12:00">
|
||||
<chapter name="Overview">
|
||||
<video name="Welcome" youtube="0.75:izygArpw-Qo,1.0:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8"/>
|
||||
<video name="Welcome" youtube="1.0:p2Q6BrNhdh8"/>
|
||||
<videosequence format="Lecture Sequence" name="System Usage Sequence">
|
||||
<html id="toylab" filename="toylab"/>
|
||||
<video name="S0V1: Video Resources" youtube="0.75:EuzkdzfR0i8,1.0:1bK-WdDi6Qw,1.25:0v1VzoDVUTM,1.50:Bxk_-ZJb240"/>
|
||||
<video name="S0V1: Video Resources" youtube="1.0:1bK-WdDi6Qw"/>
|
||||
</videosequence>
|
||||
</chapter>
|
||||
</course>
|
||||
|
||||
@@ -21,7 +21,7 @@ from models import StudentModuleCache
|
||||
from student.models import UserProfile
|
||||
from multicourse import multicourse_settings
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundError
|
||||
from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundError, NoPathToItem
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
|
||||
@@ -228,11 +228,14 @@ def jump_to(request, location):
|
||||
|
||||
# Complain if there's not data for this location
|
||||
try:
|
||||
item = modulestore().get_item(location)
|
||||
(course, chapter, section, position) = modulestore().path_to_location(location)
|
||||
except ItemNotFoundError:
|
||||
raise Http404("No data at this location: {0}".format(location))
|
||||
|
||||
return HttpResponse("O hai")
|
||||
except NoPathToItem:
|
||||
raise Http404("This location is not in any class: {0}".format(location))
|
||||
|
||||
|
||||
return index(course, chapter, section, position)
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def course_info(request, course_id):
|
||||
|
||||
Reference in New Issue
Block a user