Commit 974e66b7 by Calen Pennington

Make course ids and usage ids opaque to LMS and Studio [partial commit]

This commit updates miscellaneous files in common.

These keys are now objects with a limited interface, and the particular
internal representation is managed by the data storage layer (the
modulestore).

For the LMS, there should be no outward-facing changes to the system.
The keys are, for now, a change to internal representation only. For
Studio, the new serialized form of the keys is used in urls, to allow
for further migration in the future.

Co-Author: Andy Armstrong <andya@edx.org>
Co-Author: Christina Roberts <christina@edx.org>
Co-Author: David Baumgold <db@edx.org>
Co-Author: Diana Huang <dkh@edx.org>
Co-Author: Don Mitchell <dmitchell@edx.org>
Co-Author: Julia Hansbrough <julia@edx.org>
Co-Author: Nimisha Asthagiri <nasthagiri@edx.org>
Co-Author: Sarina Canelake <sarina@edx.org>

[LMS-2370]
parent 7852906c
......@@ -19,6 +19,7 @@ TODO:
import json
from lxml import etree
from lxml.html import fromstring
import unittest
import textwrap
import xml.sax.saxutils as saxutils
......@@ -709,9 +710,23 @@ class MatlabTest(unittest.TestCase):
the_input = self.input_class(test_capa_system(), elt, state)
context = the_input._get_render_context() # pylint: disable=W0212
self.maxDiff = None
expected = u'\n<div class="matlabResponse"><div class="commandWindowOutput" style="white-space: pre;"> <strong>if</strong> Conditionally execute statements.\nThe general form of the <strong>if</strong> statement is\n\n <strong>if</strong> expression\n statements\n ELSEIF expression\n statements\n ELSE\n statements\n END\n\nThe statements are executed if the real part of the expression\nhas all non-zero elements. The ELSE and ELSEIF parts are optional.\nZero or more ELSEIF parts can be used as well as nested <strong>if</strong>\'s.\nThe expression is usually of the form expr rop expr where\nrop is ==, &lt;, &gt;, &lt;=, &gt;=, or ~=.\n<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAjAAAAGkCAIAAACgj==">\n\nExample\n if I == J\n A(I,J) = 2;\n elseif abs(I-J) == 1\n A(I,J) = -1;\n else\n A(I,J) = 0;\n end\n\nSee also <a>relop</a>, <a>else</a>, <a>elseif</a>, <a>end</a>, <a>for</a>, <a>while</a>, <a>switch</a>.\n\nReference page in Help browser\n <a>doc if</a>\n\n</div><ul></ul></div>\n'
expected = fromstring(u'\n<div class="matlabResponse"><div class="commandWindowOutput" style="white-space: pre;"> <strong>if</strong> Conditionally execute statements.\nThe general form of the <strong>if</strong> statement is\n\n <strong>if</strong> expression\n statements\n ELSEIF expression\n statements\n ELSE\n statements\n END\n\nThe statements are executed if the real part of the expression \nhas all non-zero elements. The ELSE and ELSEIF parts are optional.\nZero or more ELSEIF parts can be used as well as nested <strong>if</strong>\'s.\nThe expression is usually of the form expr rop expr where \nrop is ==, &lt;, &gt;, &lt;=, &gt;=, or ~=.\n<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAjAAAAGkCAIAAACgj==">\n\nExample\n if I == J\n A(I,J) = 2;\n elseif abs(I-J) == 1\n A(I,J) = -1;\n else\n A(I,J) = 0;\n end\n\nSee also <a>relop</a>, <a>else</a>, <a>elseif</a>, <a>end</a>, <a>for</a>, <a>while</a>, <a>switch</a>.\n\nReference page in Help browser\n <a>doc if</a>\n\n</div><ul></ul></div>\n')
received = fromstring(context['queue_msg'])
html_tree_equal(received, expected)
self.assertEqual(context['queue_msg'], expected)
def html_tree_equal(received, expected):
"""
Returns whether two etree Elements are the same, with insensitivity to attribute order.
"""
for attr in ('tag', 'attrib', 'text', 'tail'):
if getattr(received, attr) != getattr(expected, attr):
return False
if len(received) != len(expected):
return False
if any(not html_tree_equal(rec, exp) for rec, exp in zip(received, expected)):
return False
return True
class SchematicTest(unittest.TestCase):
......
......@@ -4,11 +4,14 @@ Fixture to create a course and course components (XBlocks).
import json
import datetime
import requests
from textwrap import dedent
from collections import namedtuple
import requests
from path import path
from lazy import lazy
from xmodule.modulestore.
from . import STUDIO_BASE_URL
......@@ -235,35 +238,35 @@ class CourseFixture(StudioApiFixture):
self._install_course_handouts()
self._configure_course()
self._upload_assets()
self._create_xblock_children(self._course_loc, self._children)
self._create_xblock_children(self._course_location, self._children)
@property
def _course_loc(self):
def _course_key(self):
"""
Return the locator string for the course.
"""
return "{org}.{number}.{run}/branch/draft/block/{run}".format(**self._course_dict)
return "slashes:{org}+{number}+{run}".format(**self._course_dict)
@property
def _updates_loc(self):
def _course_location(self):
"""
Return the locator string for the course updates
Return the locator string for the course.
"""
return "{org}.{number}.{run}/branch/draft/block/updates".format(**self._course_dict)
return "location:{org}+{number}+{run}+course+{run}".format(**self._course_dict)
@property
def _assets_url(self):
"""
Return the url string for the assets
"""
return "/assets/{org}.{number}.{run}/branch/draft/block/{run}".format(**self._course_dict)
return "/assets/" + self._course_key + "/"
@property
def _handouts_loc(self):
"""
Return the locator string for the course handouts
"""
return "{org}.{number}.{run}/branch/draft/block/handouts".format(**self._course_dict)
return "location:{org}+{number}+{run}+course_info+handouts".format(**self._course_dict)
def _create_course(self):
"""
......@@ -272,7 +275,7 @@ class CourseFixture(StudioApiFixture):
# If the course already exists, this will respond
# with a 200 and an error message, which we ignore.
response = self.session.post(
STUDIO_BASE_URL + '/course',
STUDIO_BASE_URL + '/course/',
data=self._encode_post_dict(self._course_dict),
headers=self.headers
)
......@@ -298,7 +301,7 @@ class CourseFixture(StudioApiFixture):
"""
Configure course settings (e.g. start and end date)
"""
url = STUDIO_BASE_URL + '/settings/details/' + self._course_loc
url = STUDIO_BASE_URL + '/settings/details/' + self._course_key
# First, get the current values
response = self.session.get(url, headers=self.headers)
......@@ -326,8 +329,8 @@ class CourseFixture(StudioApiFixture):
if not response.ok:
raise CourseFixtureError(
"Could not update course details to '{0}'. Status was {1}.".format(
self._course_details, response.status_code))
"Could not update course details to '{0}' with {1}: Status was {2}.".format(
self._course_details, url, response.status_code))
def _install_course_handouts(self):
"""
......@@ -354,13 +357,13 @@ class CourseFixture(StudioApiFixture):
if not response.ok:
raise CourseFixtureError(
"Could not update course handouts. Status was {0}".format(response.status_code))
"Could not update course handouts with {0}. Status was {1}".format(url, response.status_code))
def _install_course_updates(self):
"""
Add updates to the course, if any are configured.
"""
url = STUDIO_BASE_URL + '/course_info_update/' + self._updates_loc
url = STUDIO_BASE_URL + '/course_info_update/' + self._course_key + '/'
for update in self._updates:
......@@ -371,8 +374,8 @@ class CourseFixture(StudioApiFixture):
if not response.ok:
raise CourseFixtureError(
"Could not add update to course: {0}. Status was {1}".format(
update, response.status_code))
"Could not add update to course: {0} with {1}. Status was {2}".format(
update, url, response.status_code))
def _upload_assets(self):
"""
......@@ -397,8 +400,8 @@ class CourseFixture(StudioApiFixture):
upload_response = self.session.post(url, files=files, headers=headers)
if not upload_response.ok:
raise CourseFixtureError('Could not upload {asset_name}. Status code: {code}'.format(
asset_name=asset_name, code=upload_response.status_code))
raise CourseFixtureError('Could not upload {asset_name} with {url}. Status code: {code}'.format(
asset_name=asset_name, url=url, code=upload_response.status_code))
def _create_xblock_children(self, parent_loc, xblock_descriptions):
"""
......@@ -425,7 +428,7 @@ class CourseFixture(StudioApiFixture):
# Create the new XBlock
response = self.session.post(
STUDIO_BASE_URL + '/xblock',
STUDIO_BASE_URL + '/xblock/',
data=json.dumps(create_payload),
headers=self.headers,
)
......
......@@ -10,7 +10,7 @@ class CoursewarePage(CoursePage):
Course info.
"""
url_path = "courseware"
url_path = "courseware/"
def is_browser_on_page(self):
return self.q(css='body.courseware').present
......@@ -34,8 +34,5 @@ class CoursePage(PageObject):
"""
Construct a URL to the page within the course.
"""
return "/".join([
BASE_URL, self.url_path,
"{course_org}.{course_num}.{course_run}".format(**self.course_info),
"branch", "draft", "block", self.course_info['course_run']
])
course_key = "slashes:{course_org}+{course_num}+{course_run}".format(**self.course_info)
return "/".join([BASE_URL, self.url_path, course_key])
......@@ -11,7 +11,7 @@ class DashboardPage(PageObject):
My Courses page in Studio
"""
url = BASE_URL + "/course"
url = BASE_URL + "/course/"
def is_browser_on_page(self):
return self.q(css='body.view-dashboard').present
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment