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: ...@@ -19,6 +19,7 @@ TODO:
import json import json
from lxml import etree from lxml import etree
from lxml.html import fromstring
import unittest import unittest
import textwrap import textwrap
import xml.sax.saxutils as saxutils import xml.sax.saxutils as saxutils
...@@ -709,9 +710,23 @@ class MatlabTest(unittest.TestCase): ...@@ -709,9 +710,23 @@ class MatlabTest(unittest.TestCase):
the_input = self.input_class(test_capa_system(), elt, state) the_input = self.input_class(test_capa_system(), elt, state)
context = the_input._get_render_context() # pylint: disable=W0212 context = the_input._get_render_context() # pylint: disable=W0212
self.maxDiff = None 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): class SchematicTest(unittest.TestCase):
......
...@@ -4,11 +4,14 @@ Fixture to create a course and course components (XBlocks). ...@@ -4,11 +4,14 @@ Fixture to create a course and course components (XBlocks).
import json import json
import datetime import datetime
import requests
from textwrap import dedent from textwrap import dedent
from collections import namedtuple from collections import namedtuple
import requests
from path import path from path import path
from lazy import lazy from lazy import lazy
from xmodule.modulestore.
from . import STUDIO_BASE_URL from . import STUDIO_BASE_URL
...@@ -235,35 +238,35 @@ class CourseFixture(StudioApiFixture): ...@@ -235,35 +238,35 @@ class CourseFixture(StudioApiFixture):
self._install_course_handouts() self._install_course_handouts()
self._configure_course() self._configure_course()
self._upload_assets() self._upload_assets()
self._create_xblock_children(self._course_loc, self._children) self._create_xblock_children(self._course_location, self._children)
@property @property
def _course_loc(self): def _course_key(self):
""" """
Return the locator string for the course. 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 @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 @property
def _assets_url(self): def _assets_url(self):
""" """
Return the url string for the assets 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 @property
def _handouts_loc(self): def _handouts_loc(self):
""" """
Return the locator string for the course handouts 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): def _create_course(self):
""" """
...@@ -272,7 +275,7 @@ class CourseFixture(StudioApiFixture): ...@@ -272,7 +275,7 @@ class CourseFixture(StudioApiFixture):
# If the course already exists, this will respond # If the course already exists, this will respond
# with a 200 and an error message, which we ignore. # with a 200 and an error message, which we ignore.
response = self.session.post( response = self.session.post(
STUDIO_BASE_URL + '/course', STUDIO_BASE_URL + '/course/',
data=self._encode_post_dict(self._course_dict), data=self._encode_post_dict(self._course_dict),
headers=self.headers headers=self.headers
) )
...@@ -298,7 +301,7 @@ class CourseFixture(StudioApiFixture): ...@@ -298,7 +301,7 @@ class CourseFixture(StudioApiFixture):
""" """
Configure course settings (e.g. start and end date) 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 # First, get the current values
response = self.session.get(url, headers=self.headers) response = self.session.get(url, headers=self.headers)
...@@ -326,8 +329,8 @@ class CourseFixture(StudioApiFixture): ...@@ -326,8 +329,8 @@ class CourseFixture(StudioApiFixture):
if not response.ok: if not response.ok:
raise CourseFixtureError( raise CourseFixtureError(
"Could not update course details to '{0}'. Status was {1}.".format( "Could not update course details to '{0}' with {1}: Status was {2}.".format(
self._course_details, response.status_code)) self._course_details, url, response.status_code))
def _install_course_handouts(self): def _install_course_handouts(self):
""" """
...@@ -354,13 +357,13 @@ class CourseFixture(StudioApiFixture): ...@@ -354,13 +357,13 @@ class CourseFixture(StudioApiFixture):
if not response.ok: if not response.ok:
raise CourseFixtureError( 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): def _install_course_updates(self):
""" """
Add updates to the course, if any are configured. 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: for update in self._updates:
...@@ -371,8 +374,8 @@ class CourseFixture(StudioApiFixture): ...@@ -371,8 +374,8 @@ class CourseFixture(StudioApiFixture):
if not response.ok: if not response.ok:
raise CourseFixtureError( raise CourseFixtureError(
"Could not add update to course: {0}. Status was {1}".format( "Could not add update to course: {0} with {1}. Status was {2}".format(
update, response.status_code)) update, url, response.status_code))
def _upload_assets(self): def _upload_assets(self):
""" """
...@@ -397,8 +400,8 @@ class CourseFixture(StudioApiFixture): ...@@ -397,8 +400,8 @@ class CourseFixture(StudioApiFixture):
upload_response = self.session.post(url, files=files, headers=headers) upload_response = self.session.post(url, files=files, headers=headers)
if not upload_response.ok: if not upload_response.ok:
raise CourseFixtureError('Could not upload {asset_name}. Status code: {code}'.format( raise CourseFixtureError('Could not upload {asset_name} with {url}. Status code: {code}'.format(
asset_name=asset_name, code=upload_response.status_code)) asset_name=asset_name, url=url, code=upload_response.status_code))
def _create_xblock_children(self, parent_loc, xblock_descriptions): def _create_xblock_children(self, parent_loc, xblock_descriptions):
""" """
...@@ -425,7 +428,7 @@ class CourseFixture(StudioApiFixture): ...@@ -425,7 +428,7 @@ class CourseFixture(StudioApiFixture):
# Create the new XBlock # Create the new XBlock
response = self.session.post( response = self.session.post(
STUDIO_BASE_URL + '/xblock', STUDIO_BASE_URL + '/xblock/',
data=json.dumps(create_payload), data=json.dumps(create_payload),
headers=self.headers, headers=self.headers,
) )
......
...@@ -10,7 +10,7 @@ class CoursewarePage(CoursePage): ...@@ -10,7 +10,7 @@ class CoursewarePage(CoursePage):
Course info. Course info.
""" """
url_path = "courseware" url_path = "courseware/"
def is_browser_on_page(self): def is_browser_on_page(self):
return self.q(css='body.courseware').present return self.q(css='body.courseware').present
...@@ -34,8 +34,5 @@ class CoursePage(PageObject): ...@@ -34,8 +34,5 @@ class CoursePage(PageObject):
""" """
Construct a URL to the page within the course. Construct a URL to the page within the course.
""" """
return "/".join([ course_key = "slashes:{course_org}+{course_num}+{course_run}".format(**self.course_info)
BASE_URL, self.url_path, return "/".join([BASE_URL, self.url_path, course_key])
"{course_org}.{course_num}.{course_run}".format(**self.course_info),
"branch", "draft", "block", self.course_info['course_run']
])
...@@ -11,7 +11,7 @@ class DashboardPage(PageObject): ...@@ -11,7 +11,7 @@ class DashboardPage(PageObject):
My Courses page in Studio My Courses page in Studio
""" """
url = BASE_URL + "/course" url = BASE_URL + "/course/"
def is_browser_on_page(self): def is_browser_on_page(self):
return self.q(css='body.view-dashboard').present 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