Merge branch 'master' into rc/2013-07-30
This commit is contained in:
@@ -17,10 +17,10 @@ installation process.
|
||||
Providers. You should use VirtualBox >= 4.2.12.
|
||||
(Windows: later/earlier VirtualBox versions than 4.2.12 have been reported to not work well with
|
||||
Vagrant. If this is still a problem, you can
|
||||
install 4.2.12 from https://www.virtualbox.org/wiki/Download_Old_Builds_4_2).
|
||||
install 4.2.12 from http://download.virtualbox.org/virtualbox/4.2.12/).
|
||||
4. Install Vagrant: http://www.vagrantup.com/ (Vagrant 1.2.2 or later)
|
||||
5. Open a terminal
|
||||
6. Download the project: `git clone git://github.com/edx/edx-platform.git`
|
||||
6. Download the project: `git clone https://github.com/edx/edx-platform.git`
|
||||
7. Enter the project directory: `cd edx-platform/`
|
||||
8. (Windows only) Run the commands to
|
||||
[deal with line endings and symlinks under Windows](https://github.com/edx/edx-platform/wiki/Simplified-install-with-vagrant#dealing-with-line-endings-and-symlinks-under-windows)
|
||||
|
||||
@@ -39,7 +39,7 @@ class Command(BaseCommand):
|
||||
# added with status granted above, and add_user_with_status_unrequested
|
||||
# will not try to add them again if they already exist in the course creator database.
|
||||
for user in get_users_with_staff_role():
|
||||
add_user_with_status_unrequested(admin, user)
|
||||
add_user_with_status_unrequested(user)
|
||||
|
||||
# There could be users who are not in either staff or instructor (they've
|
||||
# never actually done anything in Studio). I plan to add those as unrequested
|
||||
|
||||
@@ -1359,3 +1359,63 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
self.assertEqual(course.textbooks, fetched_course.textbooks)
|
||||
# is this test too strict? i.e., it requires the dicts to be ==
|
||||
self.assertEqual(course.checklists, fetched_course.checklists)
|
||||
|
||||
|
||||
class MetadataSaveTestCase(ModuleStoreTestCase):
|
||||
"""
|
||||
Test that metadata is correctly decached.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
sample_xml = '''
|
||||
<video display_name="Test Video"
|
||||
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
|
||||
show_captions="false"
|
||||
from="00:00:01"
|
||||
to="00:01:00">
|
||||
<source src="http://www.example.com/file.mp4"/>
|
||||
<track src="http://www.example.com/track"/>
|
||||
</video>
|
||||
'''
|
||||
CourseFactory.create(org='edX', course='999', display_name='Robot Super Course')
|
||||
course_location = Location(['i4x', 'edX', '999', 'course', 'Robot_Super_Course', None])
|
||||
|
||||
model_data = {'data': sample_xml}
|
||||
self.descriptor = ItemFactory.create(parent_location=course_location, category='video', data=model_data)
|
||||
|
||||
def test_metadata_persistence(self):
|
||||
"""
|
||||
Test that descriptors which set metadata fields in their
|
||||
constructor are correctly persisted.
|
||||
"""
|
||||
# We should start with a source field, from the XML's <source/> tag
|
||||
self.assertIn('source', own_metadata(self.descriptor))
|
||||
attrs_to_strip = {
|
||||
'show_captions',
|
||||
'youtube_id_1_0',
|
||||
'youtube_id_0_75',
|
||||
'youtube_id_1_25',
|
||||
'youtube_id_1_5',
|
||||
'start_time',
|
||||
'end_time',
|
||||
'source',
|
||||
'track'
|
||||
}
|
||||
# We strip out all metadata fields to reproduce a bug where
|
||||
# constructors which set their fields (e.g. Video) didn't have
|
||||
# those changes persisted. So in the end we have the XML data
|
||||
# in `descriptor.data`, but not in the individual fields
|
||||
fields = self.descriptor.fields
|
||||
for field in fields:
|
||||
if field.name in attrs_to_strip:
|
||||
field.delete_from(self.descriptor)
|
||||
|
||||
# Assert that we correctly stripped the field
|
||||
self.assertNotIn('source', own_metadata(self.descriptor))
|
||||
get_modulestore(self.descriptor.location).update_metadata(
|
||||
self.descriptor.location,
|
||||
own_metadata(self.descriptor)
|
||||
)
|
||||
module = get_modulestore(self.descriptor.location).get_item(self.descriptor.location)
|
||||
# Assert that get_item correctly sets the metadata
|
||||
self.assertIn('source', own_metadata(module))
|
||||
|
||||
@@ -53,6 +53,13 @@ CONTAINER_XMODULES = (
|
||||
CourseDescriptor,
|
||||
)
|
||||
|
||||
# These modules are editable in studio yet
|
||||
NOT_STUDIO_EDITABLE = (
|
||||
CrowdsourceHinterDescriptor,
|
||||
GraphicalSliderToolDescriptor,
|
||||
PollDescriptor
|
||||
)
|
||||
|
||||
|
||||
class TestXBlockWrapper(object):
|
||||
|
||||
@@ -60,12 +67,11 @@ class TestXBlockWrapper(object):
|
||||
def leaf_module_runtime(self):
|
||||
runtime = Mock()
|
||||
runtime.render_template = lambda *args, **kwargs: unicode((args, kwargs))
|
||||
runtime.anonymous_student_id = 'anonymous_student_id'
|
||||
runtime.anonymous_student_id = 'dummy_anonymous_student_id'
|
||||
runtime.open_ended_grading_interface = {}
|
||||
runtime.seed = 5
|
||||
runtime.get = lambda x: getattr(runtime, x)
|
||||
runtime.position = 2
|
||||
runtime.ajax_url = 'ajax_url'
|
||||
runtime.ajax_url = 'dummy_ajax_url'
|
||||
runtime.xblock_model_data = lambda d: d._model_data
|
||||
return runtime
|
||||
|
||||
@@ -78,7 +84,7 @@ class TestXBlockWrapper(object):
|
||||
def leaf_descriptor(self, descriptor_cls):
|
||||
return descriptor_cls(
|
||||
self.leaf_descriptor_runtime,
|
||||
{'location': 'i4x://org/course/catagory/name'}
|
||||
{'location': 'i4x://org/course/category/name'}
|
||||
)
|
||||
|
||||
def leaf_module(self, descriptor_cls):
|
||||
@@ -90,6 +96,7 @@ class TestXBlockWrapper(object):
|
||||
runtime.get_module.side_effect = lambda x: self.leaf_module(HtmlDescriptor)
|
||||
else:
|
||||
runtime.get_module.side_effect = lambda x: self.container_module(VerticalDescriptor, depth-1)
|
||||
runtime.position = 2
|
||||
return runtime
|
||||
|
||||
@property
|
||||
@@ -102,7 +109,7 @@ class TestXBlockWrapper(object):
|
||||
return descriptor_cls(
|
||||
self.container_descriptor_runtime,
|
||||
{
|
||||
'location': 'i4x://org/course/catagory/name',
|
||||
'location': 'i4x://org/course/category/name',
|
||||
'children': range(3)
|
||||
}
|
||||
)
|
||||
@@ -158,3 +165,62 @@ class TestStudentView(TestXBlockWrapper):
|
||||
def check_student_view_container_node_xblocks_only(self, descriptor_cls):
|
||||
raise SkipTest("XBlock support in XModules not yet fully implemented")
|
||||
|
||||
|
||||
class TestStudioView(TestXBlockWrapper):
|
||||
|
||||
# Test that for all of the Descriptors listed in LEAF_XMODULES,
|
||||
# the studio_view wrapper returns the same thing in its content
|
||||
# as get_html returns
|
||||
def test_studio_view_leaf_node(self):
|
||||
for descriptor_cls in LEAF_XMODULES:
|
||||
yield self.check_studio_view_leaf_node, descriptor_cls
|
||||
|
||||
# Check that when a descriptor is instantiated from descriptor_cls
|
||||
# it generates the same thing from studio_view that it does from get_html
|
||||
def check_studio_view_leaf_node(self, descriptor_cls):
|
||||
if descriptor_cls in NOT_STUDIO_EDITABLE:
|
||||
raise SkipTest(descriptor_cls.__name__ + "is not editable in studio")
|
||||
|
||||
descriptor = self.leaf_descriptor(descriptor_cls)
|
||||
assert_equal(descriptor.get_html(), descriptor.studio_view(None).content)
|
||||
|
||||
|
||||
# Test that for all of the Descriptors listed in CONTAINER_XMODULES
|
||||
# render the same thing using studio_view as they do using get_html, under the following conditions:
|
||||
# a) All of its descendents are xmodules
|
||||
# b) Some of its descendents are xmodules and some are xblocks
|
||||
# c) All of its descendents are xblocks
|
||||
def test_studio_view_container_node(self):
|
||||
for descriptor_cls in CONTAINER_XMODULES:
|
||||
yield self.check_studio_view_container_node_xmodules_only, descriptor_cls
|
||||
yield self.check_studio_view_container_node_mixed, descriptor_cls
|
||||
yield self.check_studio_view_container_node_xblocks_only, descriptor_cls
|
||||
|
||||
|
||||
# Check that when a descriptor is generated from descriptor_cls
|
||||
# with only xmodule children, it generates the same html from studio_view
|
||||
# as it does using get_html
|
||||
def check_studio_view_container_node_xmodules_only(self, descriptor_cls):
|
||||
if descriptor_cls in NOT_STUDIO_EDITABLE:
|
||||
raise SkipTest(descriptor_cls.__name__ + "is not editable in studio")
|
||||
|
||||
descriptor = self.container_descriptor(descriptor_cls)
|
||||
assert_equal(descriptor.get_html(), descriptor.studio_view(None).content)
|
||||
|
||||
# Check that when a descriptor is generated from descriptor_cls
|
||||
# with mixed xmodule and xblock children, it generates the same html from studio_view
|
||||
# as it does using get_html
|
||||
def check_studio_view_container_node_mixed(self, descriptor_cls):
|
||||
if descriptor_cls in NOT_STUDIO_EDITABLE:
|
||||
raise SkipTest(descriptor_cls.__name__ + "is not editable in studio")
|
||||
|
||||
raise SkipTest("XBlock support in XDescriptor not yet fully implemented")
|
||||
|
||||
# Check that when a descriptor is generated from descriptor_cls
|
||||
# with only xblock children, it generates the same html from studio_view
|
||||
# as it does using get_html
|
||||
def check_studio_view_container_node_xblocks_only(self, descriptor_cls):
|
||||
if descriptor_cls in NOT_STUDIO_EDITABLE:
|
||||
raise SkipTest(descriptor_cls.__name__ + "is not editable in studio")
|
||||
|
||||
raise SkipTest("XBlock support in XModules not yet fully implemented")
|
||||
|
||||
@@ -794,6 +794,18 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
|
||||
|
||||
return metadata_fields
|
||||
|
||||
# ~~~~~~~~~~~~~~~ XBlock API Wrappers ~~~~~~~~~~~~~~~~
|
||||
def studio_view(self, context):
|
||||
"""
|
||||
Return a fragment with the html from this XModuleDescriptor's editing view
|
||||
|
||||
Doesn't yet add any of the javascript to the fragment, nor the css.
|
||||
Also doesn't expect any javascript binding, yet.
|
||||
|
||||
Makes no use of the context parameter
|
||||
"""
|
||||
return Fragment(self.get_html())
|
||||
|
||||
|
||||
class DescriptorSystem(object):
|
||||
def __init__(self, load_item, resources_fs, error_tracker, **kwargs):
|
||||
|
||||
@@ -14,8 +14,8 @@ class UserSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
# This list is the minimal set required by the notification service
|
||||
fields = ("id", "email", "name")
|
||||
read_only_fields = ("id", "email")
|
||||
fields = ("id", "email", "name", "username")
|
||||
read_only_fields = ("id", "email", "username")
|
||||
|
||||
|
||||
class UserPreferenceSerializer(serializers.HyperlinkedModelSerializer):
|
||||
|
||||
@@ -74,7 +74,7 @@ class UserApiTestCase(TestCase):
|
||||
|
||||
def assertUserIsValid(self, user):
|
||||
"""Assert that the given user result is valid"""
|
||||
self.assertItemsEqual(user.keys(), ["email", "id", "name", "url"])
|
||||
self.assertItemsEqual(user.keys(), ["email", "id", "name", "username", "url"])
|
||||
self.assertSelfReferential(user)
|
||||
|
||||
def assertPrefIsValid(self, pref):
|
||||
@@ -196,7 +196,8 @@ class UserViewSetTest(UserApiTestCase):
|
||||
"email": user.email,
|
||||
"id": user.id,
|
||||
"name": user.profile.name,
|
||||
"url": uri,
|
||||
"username": user.username,
|
||||
"url": uri
|
||||
}
|
||||
)
|
||||
|
||||
@@ -334,6 +335,7 @@ class UserPreferenceViewSetTest(UserApiTestCase):
|
||||
"email": pref.user.email,
|
||||
"id": pref.user.id,
|
||||
"name": pref.user.profile.name,
|
||||
"username": pref.user.username,
|
||||
"url": self.get_uri_for_user(pref.user),
|
||||
},
|
||||
"key": pref.key,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Python libraries to install directly from github
|
||||
|
||||
# Third-party:
|
||||
-e git://github.com/edx/django-staticfiles.git@6d2504e5c8#egg=django-staticfiles
|
||||
-e git://github.com/edx/django-pipeline.git#egg=django-pipeline
|
||||
-e git://github.com/edx/django-wiki.git@41815e2ef1b0323f92900f8e60711b0f0c37766b#egg=django-wiki
|
||||
-e git://github.com/dementrock/pystache_custom.git@776973740bdaad83a3b029f96e415a7d1e8bec2f#egg=pystache_custom-dev
|
||||
-e git://github.com/eventbrite/zendesk.git@d53fe0e81b623f084e91776bcf6369f8b7b63879#egg=zendesk
|
||||
-e git+https://github.com/edx/django-staticfiles.git@6d2504e5c8#egg=django-staticfiles
|
||||
-e git+https://github.com/edx/django-pipeline.git#egg=django-pipeline
|
||||
-e git+https://github.com/edx/django-wiki.git@41815e2ef1b0323f92900f8e60711b0f0c37766b#egg=django-wiki
|
||||
-e git+https://github.com/dementrock/pystache_custom.git@776973740bdaad83a3b029f96e415a7d1e8bec2f#egg=pystache_custom-dev
|
||||
-e git+https://github.com/eventbrite/zendesk.git@d53fe0e81b623f084e91776bcf6369f8b7b63879#egg=zendesk
|
||||
|
||||
# Our libraries:
|
||||
-e git+https://github.com/edx/XBlock.git@b697bebd45deebd0f868613fab6722a0460ca0c1#egg=XBlock
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
#! /bin/bash
|
||||
# Usage: release-email-list.sh [$PREVIOUS_COMMIT [$CURRENT_COMMIT]]
|
||||
#
|
||||
# Prints a list of email addresses and a Confluence style wiki table
|
||||
# that indicate all of the changes made between $PREVIOUS_COMMIT and $CURRENT_COMMIT
|
||||
#
|
||||
# PREVIOUS_COMMIT defaults to origin/release
|
||||
# CURRENT_COMMIT defaults to HEAD
|
||||
|
||||
LOG_CMD="git --no-pager log $1..$2"
|
||||
BASE=${1:-origin/release}
|
||||
CURRENT=${2:-HEAD}
|
||||
LOG_CMD="git --no-pager log $BASE..$CURRENT"
|
||||
|
||||
RESPONSIBLE=$(sort -u <($LOG_CMD --format='tformat:%ae' && $LOG_CMD --format='tformat:%ce'))
|
||||
|
||||
echo "Comparing $BASE to $CURRENT"
|
||||
|
||||
echo "~~~~ Email ~~~~~"
|
||||
|
||||
echo -n 'To: '
|
||||
|
||||
Reference in New Issue
Block a user