Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
bce7d9e4
Commit
bce7d9e4
authored
Mar 01, 2014
by
Diana Huang
Committed by
Calen Pennington
Mar 05, 2014
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add tests and clean up A/B testing
Also fixes STUD-1351
parent
2d5c37b2
Hide whitespace changes
Inline
Side-by-side
Showing
29 changed files
with
717 additions
and
106 deletions
+717
-106
cms/djangoapps/contentstore/tests/test_import.py
+21
-0
common/djangoapps/user_api/middleware.py
+7
-2
common/djangoapps/user_api/models.py
+2
-2
common/djangoapps/user_api/tests/factories.py
+3
-1
common/djangoapps/user_api/tests/test_middleware.py
+5
-3
common/djangoapps/user_api/user_service.py
+4
-0
common/lib/xmodule/xmodule/course_module.py
+4
-2
common/lib/xmodule/xmodule/js/fixtures/split_test_staff.html
+19
-0
common/lib/xmodule/xmodule/js/js_test.yml
+1
-0
common/lib/xmodule/xmodule/js/public
+2
-0
common/lib/xmodule/xmodule/js/spec/split_test/staff_view_spec.js
+37
-0
common/lib/xmodule/xmodule/js/src/sequence/display.coffee
+6
-6
common/lib/xmodule/xmodule/modulestore/xml_importer.py
+18
-8
common/lib/xmodule/xmodule/partitions/partitions_service.py
+2
-2
common/lib/xmodule/xmodule/partitions/test_partitions.py
+148
-17
common/lib/xmodule/xmodule/public/js/split_test_staff.js
+11
-15
common/lib/xmodule/xmodule/public/js/split_test_student.js
+1
-1
common/lib/xmodule/xmodule/seq_module.py
+4
-5
common/lib/xmodule/xmodule/split_test_module.py
+38
-14
common/lib/xmodule/xmodule/tests/test_split_module.py
+63
-15
common/lib/xmodule/xmodule/tests/xml/factories.py
+5
-1
common/lib/xmodule/xmodule/x_module.py
+25
-0
common/test/data/split_test_module/course.xml
+20
-0
lms/djangoapps/courseware/tests/test_split_module.py
+244
-0
lms/lib/xblock/runtime.py
+5
-6
lms/lib/xblock/test/test_runtime.py
+11
-1
lms/templates/seq_module.html
+2
-1
lms/templates/split_test_staff_view.html
+8
-3
requirements/edx/github.txt
+1
-1
No files found.
cms/djangoapps/contentstore/tests/test_import.py
View file @
bce7d9e4
...
...
@@ -179,3 +179,24 @@ class ContentStoreImportTest(ModuleStoreTestCase):
u'i4x://testX/peergrading_copy/combinedopenended/SampleQuestion'
,
peergrading_module
.
link_to_location
)
def
test_rewrite_reference_value_dict
(
self
):
module_store
=
modulestore
(
'direct'
)
target_location
=
Location
([
'i4x'
,
'testX'
,
'split_test_copy'
,
'course'
,
'copy_run'
])
import_from_xml
(
module_store
,
'common/test/data/'
,
[
'split_test_module'
],
target_location_namespace
=
target_location
)
split_test_module
=
module_store
.
get_item
(
Location
([
'i4x'
,
'testX'
,
'split_test_copy'
,
'split_test'
,
'split1'
])
)
self
.
assertIsNotNone
(
split_test_module
)
self
.
assertEqual
(
{
"0"
:
"i4x://testX/split_test_copy/vertical/sample_0"
,
"2"
:
"i4x://testX/split_test_copy/vertical/sample_2"
,
},
split_test_module
.
group_id_to_child
,
)
common/djangoapps/user_api/middleware.py
View file @
bce7d9e4
"""
Middleware for user api.
Adds user's tags to tracking event context.
"""
from
track.contexts
import
COURSE_REGEX
from
eventtracking
import
tracker
from
user_api.models
import
UserCourseTag
class
UserTagsEventContextMiddleware
(
object
):
"""Middleware that adds a user's tags to tracking event context."""
CONTEXT_NAME
=
'user_tags_context'
def
process_request
(
self
,
request
):
...
...
@@ -41,4 +47,4 @@ class UserTagsEventContextMiddleware(object):
except
:
# pylint: disable=bare-except
pass
return
response
\ No newline at end of file
return
response
common/djangoapps/user_api/models.py
View file @
bce7d9e4
...
...
@@ -8,7 +8,7 @@ class UserPreference(models.Model):
key
=
models
.
CharField
(
max_length
=
255
,
db_index
=
True
)
value
=
models
.
TextField
()
class
Meta
:
class
Meta
:
# pylint: disable=missing-docstring
unique_together
=
(
"user"
,
"key"
)
@classmethod
...
...
@@ -45,5 +45,5 @@ class UserCourseTag(models.Model):
course_id
=
models
.
CharField
(
max_length
=
255
,
db_index
=
True
)
value
=
models
.
TextField
()
class
Meta
:
class
Meta
:
# pylint: disable=missing-docstring
unique_together
=
(
"user"
,
"course_id"
,
"key"
)
common/djangoapps/user_api/tests/factories.py
View file @
bce7d9e4
"""Provides factories for User API models."""
from
factory.django
import
DjangoModelFactory
from
factory
import
SubFactory
from
student.tests.factories
import
UserFactory
from
user_api.models
import
UserPreference
,
UserCourseTag
# Factories don't have __init__ methods, and are self documenting
# pylint: disable=W0232, C0111
class
UserPreferenceFactory
(
DjangoModelFactory
):
FACTORY_FOR
=
UserPreference
...
...
common/djangoapps/user_api/tests/test_middleware.py
View file @
bce7d9e4
"""Tests for user API middleware"""
from
mock
import
Mock
,
patch
from
unittest
import
TestCase
from
django.http
import
HttpRe
quest
,
HttpRe
sponse
from
django.http
import
HttpResponse
from
django.test.client
import
RequestFactory
from
student.tests.factories
import
UserFactory
,
AnonymousUserFactory
...
...
@@ -41,7 +42,8 @@ class TagsMiddlewareTest(TestCase):
self
.
assertEquals
(
self
.
middleware
.
process_request
(
self
.
request
),
None
)
def
assertContextSetTo
(
self
,
context
):
self
.
tracker
.
get_tracker
.
return_value
.
enter_context
.
assert_called_with
(
"""Asserts UserTagsEventContextMiddleware.CONTEXT_NAME matches ``context``"""
self
.
tracker
.
get_tracker
.
return_value
.
enter_context
.
assert_called_with
(
# pylint: disable=maybe-no-member
UserTagsEventContextMiddleware
.
CONTEXT_NAME
,
context
)
...
...
@@ -98,7 +100,7 @@ class TagsMiddlewareTest(TestCase):
self
.
assertContextSetTo
({
'course_id'
:
self
.
course_id
,
'course_user_tags'
:
{}})
def
test_remove_context
(
self
):
get_tracker
=
self
.
tracker
.
get_tracker
get_tracker
=
self
.
tracker
.
get_tracker
# pylint: disable=maybe-no-member
exit_context
=
get_tracker
.
return_value
.
exit_context
# The middleware should clean up the context when the request is done
...
...
common/djangoapps/user_api/user_service.py
View file @
bce7d9e4
...
...
@@ -9,6 +9,10 @@ UserCourseTag model.
from
user_api.models
import
UserCourseTag
# Scopes
# (currently only allows per-course tags. Can be expanded to support
# global tags (e.g. using the existing UserPreferences table))
COURSE_SCOPE
=
'course'
def
get_course_tag
(
user
,
course_id
,
key
):
"""
...
...
common/lib/xmodule/xmodule/course_module.py
View file @
bce7d9e4
...
...
@@ -158,6 +158,7 @@ class TextbookList(List):
class
UserPartitionList
(
List
):
"""Special List class for listing UserPartitions"""
def
from_json
(
self
,
values
):
return
[
UserPartition
.
from_json
(
v
)
for
v
in
values
]
...
...
@@ -175,8 +176,9 @@ class CourseFields(object):
# advanced_settings.
user_partitions
=
UserPartitionList
(
help
=
"List of user partitions of this course into groups, used e.g. for experiments"
,
default
=
[],
scope
=
Scope
.
content
)
default
=
[],
scope
=
Scope
.
content
)
wiki_slug
=
String
(
help
=
"Slug that points to the wiki for this course"
,
scope
=
Scope
.
content
)
enrollment_start
=
Date
(
help
=
"Date that enrollment for this class is opened"
,
scope
=
Scope
.
settings
)
...
...
common/lib/xmodule/xmodule/js/fixtures/split_test_staff.html
0 → 100644
View file @
bce7d9e4
<div
class=
"split-test-view"
id=
"split-test"
>
<select
class=
"split-test-select"
>
<option
value=
"0"
>
Group 0
</option>
<option
value=
"1"
>
Group 1
</option>
<option
value=
"2"
>
Group 2
</option>
</select>
<div
class=
"split-test-child"
data-group-id=
"0"
>
<
div class='condition-text'
>
condition 0
<
/div
>
</div>
<div
class=
"split-test-child"
data-group-id=
"1"
>
<
div class='condition-text'
>
condition 1
<
/div
>
</div>
<div
class=
"split-test-child"
data-group-id=
"2"
>
<
div class='condition-text'
>
condition 2
<
/div
>
</div>
<div
class=
'split-test-child-container'
></div>
</div>
common/lib/xmodule/xmodule/js/js_test.yml
View file @
bce7d9e4
...
...
@@ -57,6 +57,7 @@ lib_paths:
-
common_static/js/vendor/analytics.js
-
common_static/js/test/add_ajax_prefix.js
-
common_static/js/src/utility.js
-
public/js/split_test_staff.js
# Paths to spec (test) JavaScript files
spec_paths
:
...
...
common/lib/xmodule/xmodule/js/public
0 → 120000
View file @
bce7d9e4
../public/
\ No newline at end of file
common/lib/xmodule/xmodule/js/spec/split_test/staff_view_spec.js
0 → 100644
View file @
bce7d9e4
describe
(
'Tests for split_test staff view switching'
,
function
()
{
var
ab_module
;
var
elem
;
beforeEach
(
function
()
{
loadFixtures
(
'split_test_staff.html'
);
elem
=
$
(
'#split-test'
);
window
.
XBlock
=
jasmine
.
createSpyObj
(
'XBlock'
,
[
'initializeBlocks'
]);
ab_module
=
ABTestSelector
(
null
,
elem
);
});
afterEach
(
function
()
{
delete
window
.
XBlock
;
});
it
(
"test that we have only one visible condition"
,
function
()
{
var
containers
=
elem
.
find
(
'.split-test-child-container'
).
length
;
var
conditions_shown
=
elem
.
find
(
'.split-test-child-container .condition-text'
).
length
;
expect
(
containers
).
toEqual
(
1
);
expect
(
conditions_shown
).
toEqual
(
1
);
expect
(
XBlock
.
initializeBlocks
).
toHaveBeenCalled
();
});
it
(
"test that the right child is visible when selected"
,
function
()
{
var
groups
=
[
'0'
,
'1'
,
'2'
];
for
(
var
i
=
0
;
i
<
groups
.
length
;
i
++
)
{
var
to_select
=
groups
[
i
];
elem
.
find
(
'.split-test-select'
).
val
(
to_select
).
change
();
var
child_text
=
elem
.
find
(
'.split-test-child-container .condition-text'
).
text
();
expect
(
child_text
).
toContain
(
to_select
);
expect
(
XBlock
.
initializeBlocks
).
toHaveBeenCalled
();
}
});
});
common/lib/xmodule/xmodule/js/src/sequence/display.coffee
View file @
bce7d9e4
...
...
@@ -23,8 +23,8 @@ class @Sequence
updatePageTitle
:
->
# update the page title to include the current section
position_link
=
@
link_for
(
@
position
)
if
position_link
and
position_link
.
attr
(
'
title'
)
document
.
title
=
position_link
.
attr
(
'
title'
)
+
@
base_page_title
if
position_link
and
position_link
.
data
(
'page-
title'
)
document
.
title
=
position_link
.
data
(
'page-
title'
)
+
@
base_page_title
hookUpProgressEvent
:
->
$
(
'.problems-wrapper'
).
bind
'progressChanged'
,
@
updateProgress
...
...
@@ -98,10 +98,10 @@ class @Sequence
# Added for aborting video bufferization, see ../video/10_main.js
@
el
.
trigger
"sequence:change"
@
mark_active
new_position
current_tab
=
@
contents
.
eq
(
new_position
-
1
)
@
content_container
.
html
(
current_tab
.
text
()).
attr
(
"aria-labelledby"
,
current_tab
.
attr
(
"aria-labelledby"
))
XBlock
.
initializeBlocks
(
@
content_container
)
window
.
update_schematics
()
# For embedded circuit simulator exercises in 6.002x
...
...
@@ -115,8 +115,8 @@ class @Sequence
sequence_links
.
click
@
goto
# Focus on the first available xblock.
@
content_container
.
find
(
'.vert .xblock :first'
).
focus
()
@
$
(
"a.active"
).
blur
()
@
$
(
"a.active"
).
blur
()
goto
:
(
event
)
=>
event
.
preventDefault
()
if
$
(
event
.
target
).
hasClass
'seqnav'
# Links from courseware <a class='seqnav' href='n'>...</a>
...
...
common/lib/xmodule/xmodule/modulestore/xml_importer.py
View file @
bce7d9e4
...
...
@@ -6,7 +6,7 @@ import json
from
.xml
import
XMLModuleStore
,
ImportSystem
,
ParentTracker
from
xmodule.modulestore
import
Location
from
xblock.fields
import
Scope
,
Reference
,
ReferenceList
from
xblock.fields
import
Scope
,
Reference
,
ReferenceList
,
ReferenceValueDict
from
xmodule.contentstore.content
import
StaticContent
from
.inheritance
import
own_metadata
from
xmodule.errortracker
import
make_error_tracker
...
...
@@ -547,15 +547,25 @@ def remap_namespace(module, target_location_namespace):
)
.
url
()
return
new_ref
for
field
in
all_fields
:
if
isinstance
(
module
.
fields
.
get
(
field
),
Reference
):
new_ref
=
convert_ref
(
getattr
(
module
,
field
))
setattr
(
module
,
field
,
new_ref
)
for
field_name
in
all_fields
:
field_object
=
module
.
fields
.
get
(
field_name
)
if
isinstance
(
field_object
,
Reference
):
new_ref
=
convert_ref
(
getattr
(
module
,
field_name
))
setattr
(
module
,
field_name
,
new_ref
)
module
.
save
()
elif
isinstance
(
module
.
fields
.
get
(
field
)
,
ReferenceList
):
references
=
getattr
(
module
,
field
)
elif
isinstance
(
field_object
,
ReferenceList
):
references
=
getattr
(
module
,
field
_name
)
new_references
=
[
convert_ref
(
reference
)
for
reference
in
references
]
setattr
(
module
,
field
,
new_references
)
setattr
(
module
,
field_name
,
new_references
)
module
.
save
()
elif
isinstance
(
field_object
,
ReferenceValueDict
):
reference_dict
=
getattr
(
module
,
field_name
)
new_reference_dict
=
{
key
:
convert_ref
(
reference
)
for
key
,
reference
in
reference_dict
.
items
()
}
setattr
(
module
,
field_name
,
new_reference_dict
)
module
.
save
()
return
module
...
...
common/lib/xmodule/xmodule/partitions/partitions_service.py
View file @
bce7d9e4
...
...
@@ -88,7 +88,7 @@ class PartitionService(object):
and persist the info.
"""
key
=
self
.
_key_for_partition
(
user_partition
)
scope
=
self
.
_user_tags_service
.
COURSE
scope
=
self
.
_user_tags_service
.
COURSE
_SCOPE
group_id
=
self
.
_user_tags_service
.
get_tag
(
scope
,
key
)
if
group_id
is
not
None
:
...
...
@@ -133,6 +133,6 @@ class PartitionService(object):
'partition_name'
:
user_partition
.
name
}
# TODO: Use the XBlock publish api instead
self
.
_track_function
(
'
edx.split_test
.assigned_user_to_partition'
,
event_info
)
self
.
_track_function
(
'
xmodule.partitions
.assigned_user_to_partition'
,
event_info
)
return
group
.
id
common/lib/xmodule/xmodule/partitions/test_partitions.py
View file @
bce7d9e4
...
...
@@ -3,6 +3,7 @@ Test the partitions and partitions service
"""
from
collections
import
defaultdict
from
unittest
import
TestCase
from
mock
import
Mock
,
MagicMock
...
...
@@ -49,6 +50,131 @@ class TestGroup(TestCase):
self
.
assertEqual
(
group
.
id
,
test_id
)
self
.
assertEqual
(
group
.
name
,
name
)
def
test_from_json_broken
(
self
):
test_id
=
5
name
=
"Grendel"
# Bad version
jsonified
=
{
"id"
:
test_id
,
"name"
:
name
,
"version"
:
9001
}
with
self
.
assertRaisesRegexp
(
TypeError
,
"has unexpected version"
):
group
=
Group
.
from_json
(
jsonified
)
# Missing key "id"
jsonified
=
{
"name"
:
name
,
"version"
:
Group
.
VERSION
}
with
self
.
assertRaisesRegexp
(
TypeError
,
"missing value key 'id'"
):
group
=
Group
.
from_json
(
jsonified
)
# Has extra key - should not be a problem
jsonified
=
{
"id"
:
test_id
,
"name"
:
name
,
"version"
:
Group
.
VERSION
,
"programmer"
:
"Cale"
}
group
=
Group
.
from_json
(
jsonified
)
self
.
assertNotIn
(
"programmer"
,
group
.
to_json
())
class
TestUserPartition
(
TestCase
):
"""Test constructing UserPartitions"""
def
test_construct
(
self
):
groups
=
[
Group
(
0
,
'Group 1'
),
Group
(
1
,
'Group 2'
)]
user_partition
=
UserPartition
(
0
,
'Test Partition'
,
'for testing purposes'
,
groups
)
self
.
assertEqual
(
user_partition
.
id
,
0
)
self
.
assertEqual
(
user_partition
.
name
,
"Test Partition"
)
self
.
assertEqual
(
user_partition
.
description
,
"for testing purposes"
)
self
.
assertEqual
(
user_partition
.
groups
,
groups
)
def
test_string_id
(
self
):
groups
=
[
Group
(
0
,
'Group 1'
),
Group
(
1
,
'Group 2'
)]
user_partition
=
UserPartition
(
"70"
,
'Test Partition'
,
'for testing purposes'
,
groups
)
self
.
assertEqual
(
user_partition
.
id
,
70
)
def
test_to_json
(
self
):
groups
=
[
Group
(
0
,
'Group 1'
),
Group
(
1
,
'Group 2'
)]
upid
=
0
upname
=
"Test Partition"
updesc
=
"for testing purposes"
user_partition
=
UserPartition
(
upid
,
upname
,
updesc
,
groups
)
jsonified
=
user_partition
.
to_json
()
act_jsonified
=
{
"id"
:
upid
,
"name"
:
upname
,
"description"
:
updesc
,
"groups"
:
[
group
.
to_json
()
for
group
in
groups
],
"version"
:
user_partition
.
VERSION
}
self
.
assertEqual
(
jsonified
,
act_jsonified
)
def
test_from_json
(
self
):
groups
=
[
Group
(
0
,
'Group 1'
),
Group
(
1
,
'Group 2'
)]
upid
=
1
upname
=
"Test Partition"
updesc
=
"For Testing Purposes"
jsonified
=
{
"id"
:
upid
,
"name"
:
upname
,
"description"
:
updesc
,
"groups"
:
[
group
.
to_json
()
for
group
in
groups
],
"version"
:
UserPartition
.
VERSION
}
user_partition
=
UserPartition
.
from_json
(
jsonified
)
self
.
assertEqual
(
user_partition
.
id
,
upid
)
self
.
assertEqual
(
user_partition
.
name
,
upname
)
self
.
assertEqual
(
user_partition
.
description
,
updesc
)
for
act_group
in
user_partition
.
groups
:
self
.
assertIn
(
act_group
.
id
,
[
0
,
1
])
exp_group
=
groups
[
act_group
.
id
]
self
.
assertEqual
(
exp_group
.
id
,
act_group
.
id
)
self
.
assertEqual
(
exp_group
.
name
,
act_group
.
name
)
def
test_from_json_broken
(
self
):
groups
=
[
Group
(
0
,
'Group 1'
),
Group
(
1
,
'Group 2'
)]
upid
=
1
upname
=
"Test Partition"
updesc
=
"For Testing Purposes"
# Missing field
jsonified
=
{
"name"
:
upname
,
"description"
:
updesc
,
"groups"
:
[
group
.
to_json
()
for
group
in
groups
],
"version"
:
UserPartition
.
VERSION
}
with
self
.
assertRaisesRegexp
(
TypeError
,
"missing value key 'id'"
):
user_partition
=
UserPartition
.
from_json
(
jsonified
)
# Wrong version (it's over 9000!)
jsonified
=
{
'id'
:
upid
,
"name"
:
upname
,
"description"
:
updesc
,
"groups"
:
[
group
.
to_json
()
for
group
in
groups
],
"version"
:
9001
}
with
self
.
assertRaisesRegexp
(
TypeError
,
"has unexpected version"
):
user_partition
=
UserPartition
.
from_json
(
jsonified
)
# Has extra key - should not be a problem
jsonified
=
{
'id'
:
upid
,
"name"
:
upname
,
"description"
:
updesc
,
"groups"
:
[
group
.
to_json
()
for
group
in
groups
],
"version"
:
UserPartition
.
VERSION
,
"programmer"
:
"Cale"
}
user_partition
=
UserPartition
.
from_json
(
jsonified
)
self
.
assertNotIn
(
"programmer"
,
user_partition
.
to_json
())
class
StaticPartitionService
(
PartitionService
):
"""
...
...
@@ -63,32 +189,37 @@ class StaticPartitionService(PartitionService):
return
self
.
_partitions
class
MemoryUserTagsService
(
object
):
"""
An implementation of a user_tags XBlock service that
uses an in-memory dictionary for storage
"""
COURSE_SCOPE
=
'course'
def
__init__
(
self
):
self
.
_tags
=
defaultdict
(
dict
)
def
get_tag
(
self
,
scope
,
key
):
"""Sets the value of ``key`` to ``value``"""
print
'GETTING'
,
scope
,
key
,
self
.
_tags
return
self
.
_tags
[
scope
]
.
get
(
key
)
def
set_tag
(
self
,
scope
,
key
,
value
):
"""Gets the value of ``key``"""
self
.
_tags
[
scope
][
key
]
=
value
print
'SET'
,
scope
,
key
,
value
,
self
.
_tags
class
TestPartitionsService
(
TestCase
):
"""
Test getting a user's group out of a partition
"""
def
setUp
(
self
):
groups
=
[
Group
(
0
,
'Group 1'
),
Group
(
1
,
'Group 2'
)]
self
.
partition_id
=
0
# construct the user_service
self
.
user_tags
=
dict
()
self
.
user_tags_service
=
MagicMock
()
def
mock_set_tag
(
_scope
,
key
,
value
):
"""Sets the value of ``key`` to ``value``"""
self
.
user_tags
[
key
]
=
value
def
mock_get_tag
(
_scope
,
key
):
"""Gets the value of ``key``"""
if
key
in
self
.
user_tags
:
return
self
.
user_tags
[
key
]
return
None
self
.
user_tags_service
.
set_tag
=
mock_set_tag
self
.
user_tags_service
.
get_tag
=
mock_get_tag
self
.
user_tags_service
=
MemoryUserTagsService
()
user_partition
=
UserPartition
(
self
.
partition_id
,
'Test Partition'
,
'for testing purposes'
,
groups
)
self
.
partitions_service
=
StaticPartitionService
(
...
...
common/lib/xmodule/xmodule/public/js/split_test_staff.js
View file @
bce7d9e4
...
...
@@ -4,30 +4,26 @@
* @constructor
*/
function
ABTestSelector
(
elem
)
{
me
=
this
;
me
.
elem
=
$
(
elem
);
function
ABTestSelector
(
runtime
,
elem
)
{
var
_this
=
this
;
_this
.
elem
=
$
(
elem
);
_this
.
children
=
_this
.
elem
.
find
(
'.split-test-child'
);
_this
.
content_container
=
_this
.
elem
.
find
(
'.split-test-child-container'
);
select_child
=
function
(
group_id
)
{
function
select_child
(
group_id
)
{
// iterate over all the children and hide all the ones that haven't been selected
// and show the one that was selected
me
.
elem
.
find
(
'.split-test-child'
)
.
each
(
function
()
{
_this
.
children
.
each
(
function
()
{
// force this id to remain a string, even if it looks like something else
child_group_id
=
$
(
this
).
data
(
'group-id'
).
toString
();
var
child_group_id
=
$
(
this
).
data
(
'group-id'
).
toString
();
if
(
child_group_id
===
group_id
)
{
$
(
this
).
show
();
_this
.
content_container
.
html
(
$
(
this
).
text
());
XBlock
.
initializeBlocks
(
_this
.
content_container
);
}
else
{
$
(
this
).
hide
();
}
});
}
// hide all the children
me
.
elem
.
find
(
'.split-test-child'
).
hide
();
select
=
me
.
elem
.
find
(
'.split-test-select'
);
select
=
_this
.
elem
.
find
(
'.split-test-select'
);
cur_group_id
=
select
.
val
();
select_child
(
cur_group_id
);
...
...
common/lib/xmodule/xmodule/public/js/split_test_student.js
View file @
bce7d9e4
/* Javascript for the
Acid
XBlock. */
/* Javascript for the
Split Test
XBlock. */
function
SplitTestStudentView
(
runtime
,
element
)
{
$
.
post
(
runtime
.
handlerUrl
(
element
,
'log_child_render'
));
return
{};
...
...
common/lib/xmodule/xmodule/seq_module.py
View file @
bce7d9e4
...
...
@@ -88,13 +88,12 @@ class SequenceModule(SequenceFields, XModule):
rendered_child
=
child
.
render
(
'student_view'
,
context
)
fragment
.
add_frag_resources
(
rendered_child
)
titles
=
child
.
get_content_titles
()
print
titles
childinfo
=
{
'content'
:
rendered_child
.
content
,
'title'
:
"
\n
"
.
join
(
grand_child
.
display_name
for
grand_child
in
child
.
get_children
()
if
grand_child
.
display_name
is
not
None
),
'title'
:
"
\n
"
.
join
(
titles
),
'page_title'
:
titles
[
0
]
if
titles
else
''
,
'progress_status'
:
Progress
.
to_js_status_str
(
progress
),
'progress_detail'
:
Progress
.
to_js_detail_str
(
progress
),
'type'
:
child
.
get_icon_class
(),
...
...
common/lib/xmodule/xmodule/split_test_module.py
View file @
bce7d9e4
...
...
@@ -12,15 +12,18 @@ from xmodule.x_module import XModule, module_attr
from
lxml
import
etree
from
xblock.core
import
XBlock
from
xblock.fields
import
Scope
,
Integer
,
Dict
from
xblock.fields
import
Scope
,
Integer
,
ReferenceValue
Dict
from
xblock.fragment
import
Fragment
log
=
logging
.
getLogger
(
'edx.'
+
__name__
)
class
SplitTestFields
(
object
):
user_partition_id
=
Integer
(
help
=
"Which user partition is used for this test"
,
scope
=
Scope
.
content
)
"""Fields needed for split test module"""
user_partition_id
=
Integer
(
help
=
"Which user partition is used for this test"
,
scope
=
Scope
.
content
)
# group_id is an int
# child is a serialized UsageId (aka Location). This child
...
...
@@ -31,9 +34,10 @@ class SplitTestFields(object):
# TODO: is there a way to add some validation around this, to
# be run on course load or in studio or ....
group_id_to_child
=
Dict
(
help
=
"Which child module students in a particular "
"group_id should see"
,
scope
=
Scope
.
content
)
group_id_to_child
=
ReferenceValueDict
(
help
=
"Which child module students in a particular group_id should see"
,
scope
=
Scope
.
content
)
@XBlock.needs
(
'user_tags'
)
...
...
@@ -79,6 +83,25 @@ class SplitTestModule(SplitTestFields, XModule):
return
None
def
get_content_titles
(
self
):
"""
Returns list of content titles for split_test's child.
This overwrites the get_content_titles method included in x_module by default.
WHY THIS OVERWRITE IS NECESSARY: If we fetch *all* of split_test's children,
we'll end up getting all of the possible conditions users could ever see.
Ex: If split_test shows a video to group A and HTML to group B, the
regular get_content_titles in x_module will get the title of BOTH the video
AND the HTML.
We only want the content titles that should actually be displayed to the user.
split_test's .child property contains *only* the child that should actually
be shown to the user, so we call get_content_titles() on only that child.
"""
return
self
.
child
.
get_content_titles
()
def
get_child_descriptors
(
self
):
"""
For grading--return just the chosen child.
...
...
@@ -127,13 +150,9 @@ class SplitTestModule(SplitTestFields, XModule):
fragment
.
add_content
(
self
.
system
.
render_template
(
'split_test_staff_view.html'
,
{
'items'
:
contents
,
}))
frag_js
=
"""
$(document).ready(function() {{
ABTestSelector($('.split-test-view'));
}});
"""
fragment
.
add_javascript
(
frag_js
)
fragment
.
add_css
(
'.split-test-child { display: none; }'
)
fragment
.
add_javascript_url
(
self
.
runtime
.
local_resource_url
(
self
,
'public/js/split_test_staff.js'
))
fragment
.
initialize_js
(
'ABTestSelector'
)
return
fragment
def
student_view
(
self
,
context
):
...
...
@@ -159,9 +178,12 @@ class SplitTestModule(SplitTestFields, XModule):
return
fragment
@XBlock.handler
def
log_child_render
(
self
,
request
,
suffix
=
''
):
def
log_child_render
(
self
,
request
,
suffix
=
''
):
# pylint: disable=unused-argument
"""
Record in the tracking logs which child was rendered
"""
# TODO: use publish instead, when publish is wired to the tracking logs
self
.
system
.
track_function
(
'
split-test-child-
render'
,
{
'child-id'
:
self
.
child
.
scope_ids
.
usage_id
})
self
.
system
.
track_function
(
'
xblock.split_test.child_
render'
,
{
'child-id'
:
self
.
child
.
scope_ids
.
usage_id
})
return
Response
()
def
get_icon_class
(
self
):
...
...
@@ -184,6 +206,7 @@ class SplitTestDescriptor(SplitTestFields, SequenceDescriptor):
child_descriptor
=
module_attr
(
'child_descriptor'
)
log_child_render
=
module_attr
(
'log_child_render'
)
get_content_titles
=
module_attr
(
'get_content_titles'
)
def
definition_to_xml
(
self
,
resource_fs
):
...
...
@@ -200,3 +223,4 @@ class SplitTestDescriptor(SplitTestFields, SequenceDescriptor):
makes it use module.get_child_descriptors().
"""
return
True
common/lib/xmodule/xmodule/tests/test_split_module.py
View file @
bce7d9e4
...
...
@@ -8,9 +8,7 @@ from xmodule.tests.xml import factories as xml
from
xmodule.tests.xml
import
XModuleXmlImportTest
from
xmodule.tests
import
get_test_system
from
xmodule.partitions.partitions
import
Group
,
UserPartition
from
xmodule.partitions.partitions_service
import
PartitionService
from
xmodule.partitions.test_partitions
import
StaticPartitionService
from
xmodule.partitions.test_partitions
import
StaticPartitionService
,
MemoryUserTagsService
class
SplitTestModuleFactory
(
xml
.
XmlImportFactory
):
...
...
@@ -38,15 +36,24 @@ class SplitTestModuleTest(XModuleXmlImportTest):
'group_id_to_child'
:
'{"0": "i4x://edX/xml_test_course/html/split_test_cond0", "1": "i4x://edX/xml_test_course/html/split_test_cond1"}'
}
)
xml
.
HtmlFactory
(
parent
=
split_test
,
url_name
=
'split_test_cond0'
)
xml
.
HtmlFactory
(
parent
=
split_test
,
url_name
=
'split_test_cond1'
)
xml
.
HtmlFactory
(
parent
=
split_test
,
url_name
=
'split_test_cond0'
,
text
=
'HTML FOR GROUP 0'
)
xml
.
HtmlFactory
(
parent
=
split_test
,
url_name
=
'split_test_cond1'
,
text
=
'HTML FOR GROUP 1'
)
self
.
course
=
self
.
process_xml
(
course
)
course_seq
=
self
.
course
.
get_children
()[
0
]
self
.
module_system
=
get_test_system
()
self
.
tags_service
=
Mock
(
name
=
'user_tags'
)
self
.
module_system
.
_services
[
'user_tags'
]
=
self
.
tags_service
def
get_module
(
descriptor
):
module_system
=
get_test_system
()
module_system
.
get_module
=
get_module
descriptor
.
bind_for_student
(
module_system
,
descriptor
.
_field_data
)
return
descriptor
self
.
module_system
.
get_module
=
get_module
self
.
module_system
.
descriptor_system
=
self
.
course
.
runtime
self
.
tags_service
=
MemoryUserTagsService
()
self
.
module_system
.
_services
[
'user_tags'
]
=
self
.
tags_service
# pylint: disable=protected-access
self
.
partitions_service
=
StaticPartitionService
(
[
...
...
@@ -57,18 +64,59 @@ class SplitTestModuleTest(XModuleXmlImportTest):
course_id
=
self
.
course
.
id
,
track_function
=
Mock
(
name
=
'track_function'
),
)
self
.
module_system
.
_services
[
'partitions'
]
=
self
.
partitions_service
self
.
module_system
.
_services
[
'partitions'
]
=
self
.
partitions_service
# pylint: disable=protected-access
self
.
split_test_module
=
course_seq
.
get_children
()[
0
]
self
.
split_test_module
.
bind_for_student
(
self
.
module_system
,
self
.
split_test_module
.
_field_data
)
self
.
split_test_descriptor
=
course_seq
.
get_children
()[
0
]
self
.
split_test_descriptor
.
bind_for_student
(
self
.
module_system
,
self
.
split_test_descriptor
.
_field_data
)
@ddt.data
((
'0'
,
'split_test_cond0'
),
(
'1'
,
'split_test_cond1'
))
@ddt.unpack
def
test_child
(
self
,
user_tag
,
child_url_name
):
self
.
tags_service
.
get_tag
.
return_value
=
user_tag
self
.
tags_service
.
set_tag
(
self
.
tags_service
.
COURSE_SCOPE
,
'xblock.partition_service.partition_0'
,
user_tag
)
self
.
assertEquals
(
self
.
split_test_module
.
child_descriptor
.
url_name
,
child_url_name
)
@ddt.data
((
'0'
,),
(
'1'
,))
@ddt.unpack
def
test_child_old_tag_value
(
self
,
user_tag
):
# If user_tag has a stale value, we should still get back a valid child url
self
.
tags_service
.
set_tag
(
self
.
tags_service
.
COURSE_SCOPE
,
'xblock.partition_service.partition_0'
,
'2'
)
self
.
assertEquals
(
self
.
split_test_descriptor
.
child_descriptor
.
url_name
,
child_url_name
)
self
.
assertIn
(
self
.
split_test_module
.
child_descriptor
.
url_name
,
[
'split_test_cond0'
,
'split_test_cond1'
])
@ddt.data
((
'0'
,
'HTML FOR GROUP 0'
),
(
'1'
,
'HTML FOR GROUP 1'
))
@ddt.unpack
def
test_get_html
(
self
,
user_tag
,
child_content
):
self
.
tags_service
.
set_tag
(
self
.
tags_service
.
COURSE_SCOPE
,
'xblock.partition_service.partition_0'
,
user_tag
)
self
.
assertIn
(
child_content
,
self
.
module_system
.
render
(
self
.
split_test_module
,
'student_view'
)
.
content
)
@ddt.data
((
'0'
,),
(
'1'
,))
@ddt.unpack
def
test_child_missing_tag_value
(
self
,
user_tag
):
# If user_tag has a missing value, we should still get back a valid child url
self
.
assertIn
(
self
.
split_test_module
.
child_descriptor
.
url_name
,
[
'split_test_cond0'
,
'split_test_cond1'
])
@ddt.data
((
'100'
,),
(
'200'
,),
(
'300'
,),
(
'400'
,),
(
'500'
,),
(
'600'
,),
(
'700'
,),
(
'800'
,),
(
'900'
,),
(
'1000'
,))
@ddt.unpack
def
test_child_persist_new_tag_value_when_tag_missing
(
self
,
user_tag
):
# If a user_tag has a missing value, a group should be saved/persisted for that user.
# So, we check that we get the same url_name when we call on the url_name twice.
# We run the test ten times so that, if our storage is failing, we'll be most likely to notice it.
self
.
assertEquals
(
self
.
split_test_module
.
child_descriptor
.
url_name
,
self
.
split_test_module
.
child_descriptor
.
url_name
)
common/lib/xmodule/xmodule/tests/xml/factories.py
View file @
bce7d9e4
...
...
@@ -146,6 +146,10 @@ class SequenceFactory(XmlImportFactory):
"""Factory for <sequential> nodes"""
tag
=
'sequential'
class
VerticalFactory
(
XmlImportFactory
):
"""Factory for <vertical> nodes"""
tag
=
'vertical'
class
ProblemFactory
(
XmlImportFactory
):
"""Factory for <problem> nodes"""
...
...
@@ -154,5 +158,5 @@ class ProblemFactory(XmlImportFactory):
class
HtmlFactory
(
XmlImportFactory
):
"""Factory for <
problem
> nodes"""
"""Factory for <
html
> nodes"""
tag
=
'html'
common/lib/xmodule/xmodule/x_module.py
View file @
bce7d9e4
...
...
@@ -217,6 +217,31 @@ class XModuleMixin(XBlockMixin):
self
.
save
()
return
self
.
_field_data
.
_kvs
# pylint: disable=protected-access
def
get_content_titles
(
self
):
"""
Returns list of content titles for all of self's children.
SEQUENCE
|
VERTICAL
/
\
SPLIT_TEST DISCUSSION
/
\
VIDEO A VIDEO B
Essentially, this function returns a list of display_names (e.g. content titles)
for all of the leaf nodes. In the diagram above, calling get_content_titles on
SEQUENCE would return the display_names of `VIDEO A`, `VIDEO B`, and `DISCUSSION`.
This is most obviously useful for sequence_modules, which need this list to display
tooltips to users, though in theory this should work for any tree that needs
the display_names of all its leaf nodes.
"""
if
self
.
has_children
:
return
sum
((
child
.
get_content_titles
()
for
child
in
self
.
get_children
()),
[])
else
:
return
[
self
.
display_name_with_default
]
def
get_children
(
self
):
"""Returns a list of XBlock instances for the children of
this module"""
...
...
common/test/data/split_test_module/course.xml
0 → 100644
View file @
bce7d9e4
<course
url_name=
'split_test_course'
org=
'split_test'
course=
'split_test'
>
<chapter>
<sequential>
<vertical>
<split_test
url_name=
"split1"
user_partition_id=
"0"
group_id_to_child=
'{"0": "i4x://split_test/split_test/vertical/sample_0", "2": "i4x://split_test/split_test/vertical/sample_2"}'
>
<vertical
url_name=
"sample_0"
>
<html>
Here is a prompt for group 0, please respond in the discussion.
</html>
<discussion
for=
"split test discussion 0"
id=
"split_test_d0"
discussion_category=
"Lectures"
/>
</vertical>
<vertical
url_name=
"sample_2"
>
<html>
Here is a prompt for group 2, please respond in the discussion.
</html>
<discussion
for=
"split test discussion 2"
id=
"split_test_d2"
discussion_category=
"Lectures"
/>
</vertical>
</split_test>
</vertical>
</sequential>
</chapter>
</course>
\ No newline at end of file
lms/djangoapps/courseware/tests/test_split_module.py
0 → 100644
View file @
bce7d9e4
"""
Test for split test XModule
"""
import
ddt
from
mock
import
MagicMock
,
patch
,
Mock
from
django.core.urlresolvers
import
reverse
from
django.test.utils
import
override_settings
from
student.tests.factories
import
UserFactory
,
CourseEnrollmentFactory
,
AdminFactory
from
courseware.tests.modulestore_config
import
TEST_DATA_MIXED_MODULESTORE
from
xmodule.modulestore.tests.factories
import
ItemFactory
,
CourseFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.partitions.partitions
import
Group
,
UserPartition
from
xmodule.partitions.test_partitions
import
StaticPartitionService
from
user_api.tests.factories
import
UserCourseTagFactory
from
xmodule.partitions.partitions
import
Group
,
UserPartition
@override_settings
(
MODULESTORE
=
TEST_DATA_MIXED_MODULESTORE
)
class
SplitTestBase
(
ModuleStoreTestCase
):
__test__
=
False
def
setUp
(
self
):
self
.
partition
=
UserPartition
(
0
,
'first_partition'
,
'First Partition'
,
[
Group
(
0
,
'alpha'
),
Group
(
1
,
'beta'
)
]
)
self
.
course
=
CourseFactory
.
create
(
number
=
self
.
COURSE_NUMBER
,
user_partitions
=
[
self
.
partition
]
)
self
.
chapter
=
ItemFactory
.
create
(
parent_location
=
self
.
course
.
location
,
category
=
"chapter"
,
display_name
=
"test chapter"
,
)
self
.
sequential
=
ItemFactory
.
create
(
parent_location
=
self
.
chapter
.
location
,
category
=
"sequential"
,
display_name
=
"Split Test Tests"
,
)
self
.
student
=
UserFactory
.
create
()
CourseEnrollmentFactory
.
create
(
user
=
self
.
student
,
course_id
=
self
.
course
.
id
)
self
.
client
.
login
(
username
=
self
.
student
.
username
,
password
=
'test'
)
def
_video
(
self
,
parent
,
group
):
return
ItemFactory
.
create
(
parent_location
=
parent
.
location
,
category
=
"video"
,
display_name
=
"Group {} Sees This Video"
.
format
(
group
),
)
def
_problem
(
self
,
parent
,
group
):
return
ItemFactory
.
create
(
parent_location
=
parent
.
location
,
category
=
"problem"
,
display_name
=
"Group {} Sees This Problem"
.
format
(
group
),
data
=
"<h1>No Problem Defined Yet!</h1>"
,
)
def
_html
(
self
,
parent
,
group
):
return
ItemFactory
.
create
(
parent_location
=
parent
.
location
,
category
=
"html"
,
display_name
=
"Group {} Sees This HTML"
.
format
(
group
),
data
=
"Some HTML for group {}"
.
format
(
group
),
)
def
test_split_test_0
(
self
):
self
.
_check_split_test
(
0
)
def
test_split_test_1
(
self
):
self
.
_check_split_test
(
1
)
def
_check_split_test
(
self
,
user_tag
):
tag_factory
=
UserCourseTagFactory
(
user
=
self
.
student
,
course_id
=
self
.
course
.
id
,
key
=
'xblock.partition_service.partition_{0}'
.
format
(
self
.
partition
.
id
),
value
=
str
(
user_tag
)
)
resp
=
self
.
client
.
get
(
reverse
(
'courseware_section'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
,
'chapter'
:
self
.
chapter
.
url_name
,
'section'
:
self
.
sequential
.
url_name
}
))
content
=
resp
.
content
print
content
# Assert we see the proper icon in the top display
self
.
assertIn
(
'<a class="{} inactive progress-0"'
.
format
(
self
.
ICON_CLASSES
[
user_tag
]),
content
)
# And proper tooltips
for
tooltip
in
self
.
TOOLTIPS
[
user_tag
]:
self
.
assertIn
(
tooltip
,
content
)
for
hidden
in
self
.
HIDDEN_CONTENT
[
user_tag
]:
self
.
assertNotIn
(
hidden
,
content
)
# Assert that we can see the data from the appropriate test condition
for
visible
in
self
.
VISIBLE_CONTENT
[
user_tag
]:
self
.
assertIn
(
visible
,
content
)
class
TestVertSplitTestVert
(
SplitTestBase
):
"""
Tests related to xmodule/split_test_module
"""
__test__
=
True
COURSE_NUMBER
=
'vert-split-vert'
ICON_CLASSES
=
[
'seq_problem'
,
'seq_video'
,
]
TOOLTIPS
=
[
[
'Group 0 Sees This Video'
,
"Group 0 Sees This Problem"
],
[
'Group 1 Sees This Video'
,
'Group 1 Sees This HTML'
],
]
HIDDEN_CONTENT
=
[
[
'Condition 0 vertical'
],
[
'Condition 1 vertical'
],
]
# Data is html encoded, because it's inactive inside the
# sequence until javascript is executed
VISIBLE_CONTENT
=
[
[
'class="problems-wrapper'
],
[
'Some HTML for group 1'
]
]
def
setUp
(
self
):
super
(
TestVertSplitTestVert
,
self
)
.
setUp
()
# vert <- split_test
# split_test cond 0 = vert <- {video, problem}
# split_test cond 1 = vert <- {video, html}
vert1
=
ItemFactory
.
create
(
parent_location
=
self
.
sequential
.
location
,
category
=
"vertical"
,
display_name
=
"Split test vertical"
,
)
c0_url
=
self
.
course
.
location
.
_replace
(
category
=
"vertical"
,
name
=
"split_test_cond0"
)
c1_url
=
self
.
course
.
location
.
_replace
(
category
=
"vertical"
,
name
=
"split_test_cond1"
)
split_test
=
ItemFactory
.
create
(
parent_location
=
vert1
.
location
,
category
=
"split_test"
,
display_name
=
"Split test"
,
user_partition_id
=
'0'
,
group_id_to_child
=
{
"0"
:
c0_url
.
url
(),
"1"
:
c1_url
.
url
()},
)
cond0vert
=
ItemFactory
.
create
(
parent_location
=
split_test
.
location
,
category
=
"vertical"
,
display_name
=
"Condition 0 vertical"
,
location
=
c0_url
,
)
video0
=
self
.
_video
(
cond0vert
,
0
)
problem0
=
self
.
_problem
(
cond0vert
,
0
)
cond1vert
=
ItemFactory
.
create
(
parent_location
=
split_test
.
location
,
category
=
"vertical"
,
display_name
=
"Condition 1 vertical"
,
location
=
c1_url
,
)
video1
=
self
.
_video
(
cond1vert
,
1
)
html1
=
self
.
_html
(
cond1vert
,
1
)
class
TestSplitTestVert
(
SplitTestBase
):
"""
Tests related to xmodule/split_test_module
"""
__test__
=
True
COURSE_NUMBER
=
'split-vert'
ICON_CLASSES
=
[
'seq_problem'
,
'seq_video'
,
]
TOOLTIPS
=
[
[
'Group 0 Sees This Video'
,
"Group 0 Sees This Problem"
],
[
'Group 1 Sees This Video'
,
'Group 1 Sees This HTML'
],
]
HIDDEN_CONTENT
=
[
[
'Condition 0 vertical'
],
[
'Condition 1 vertical'
],
]
# Data is html encoded, because it's inactive inside the
# sequence until javascript is executed
VISIBLE_CONTENT
=
[
[
'class="problems-wrapper'
],
[
'Some HTML for group 1'
]
]
def
setUp
(
self
):
super
(
TestSplitTestVert
,
self
)
.
setUp
()
# split_test cond 0 = vert <- {video, problem}
# split_test cond 1 = vert <- {video, html}
c0_url
=
self
.
course
.
location
.
_replace
(
category
=
"vertical"
,
name
=
"split_test_cond0"
)
c1_url
=
self
.
course
.
location
.
_replace
(
category
=
"vertical"
,
name
=
"split_test_cond1"
)
split_test
=
ItemFactory
.
create
(
parent_location
=
self
.
sequential
.
location
,
category
=
"split_test"
,
display_name
=
"Split test"
,
user_partition_id
=
'0'
,
group_id_to_child
=
{
"0"
:
c0_url
.
url
(),
"1"
:
c1_url
.
url
()},
)
cond0vert
=
ItemFactory
.
create
(
parent_location
=
split_test
.
location
,
category
=
"vertical"
,
display_name
=
"Condition 0 vertical"
,
location
=
c0_url
,
)
video0
=
self
.
_video
(
cond0vert
,
0
)
problem0
=
self
.
_problem
(
cond0vert
,
0
)
cond1vert
=
ItemFactory
.
create
(
parent_location
=
split_test
.
location
,
category
=
"vertical"
,
display_name
=
"Condition 1 vertical"
,
location
=
c1_url
,
)
video1
=
self
.
_video
(
cond1vert
,
1
)
html1
=
self
.
_html
(
cond1vert
,
1
)
lms/lib/xblock/runtime.py
View file @
bce7d9e4
...
...
@@ -133,15 +133,14 @@ class UserTagsService(object):
A runtime class that provides an interface to the user service. It handles filling in
the current course id and current user.
"""
# Scopes
# (currently only allows per-course tags. Can be expanded to support
# global tags (e.g. using the existing UserPreferences table))
COURSE
=
'course'
COURSE_SCOPE
=
user_service
.
COURSE_SCOPE
def
__init__
(
self
,
runtime
):
self
.
runtime
=
runtime
def
_get_current_user
(
self
):
"""Returns the real, not anonymized, current user."""
real_user
=
self
.
runtime
.
get_real_user
(
self
.
runtime
.
anonymous_student_id
)
return
real_user
...
...
@@ -152,7 +151,7 @@ class UserTagsService(object):
scope: the current scope of the runtime
key: the key for the value we want
"""
if
scope
!=
self
.
COURS
E
:
if
scope
!=
user_service
.
COURSE_SCOP
E
:
raise
ValueError
(
"unexpected scope {0}"
.
format
(
scope
))
return
user_service
.
get_course_tag
(
self
.
_get_current_user
(),
...
...
@@ -166,7 +165,7 @@ class UserTagsService(object):
key: the key that to the value to be set
value: the value to set
"""
if
scope
!=
self
.
COURS
E
:
if
scope
!=
user_service
.
COURSE_SCOP
E
:
raise
ValueError
(
"unexpected scope {0}"
.
format
(
scope
))
return
user_service
.
set_course_tag
(
self
.
_get_current_user
(),
...
...
lms/lib/xblock/test/test_runtime.py
View file @
bce7d9e4
...
...
@@ -88,7 +88,8 @@ class TestHandlerUrl(TestCase):
self
.
assertIn
(
'handler_a'
,
self
.
_parsed_path
(
'handler_a'
))
class
TestUserServiceInterface
(
TestCase
):
class
TestUserServiceAPI
(
TestCase
):
"""Test the user service interface"""
def
setUp
(
self
):
self
.
course_id
=
"org/course/run"
...
...
@@ -97,6 +98,7 @@ class TestUserServiceInterface(TestCase):
self
.
user
.
save
()
def
mock_get_real_user
(
_anon_id
):
"""Just returns the test user"""
return
self
.
user
self
.
runtime
=
LmsModuleSystem
(
...
...
@@ -126,3 +128,11 @@ class TestUserServiceInterface(TestCase):
tag
=
self
.
runtime
.
service
(
self
.
mock_block
,
'user_tags'
)
.
get_tag
(
self
.
scope
,
self
.
key
)
self
.
assertEqual
(
tag
,
set_value
)
# Try to set tag in wrong scope
with
self
.
assertRaises
(
ValueError
):
self
.
runtime
.
service
(
self
.
mock_block
,
'user_tags'
)
.
set_tag
(
'fake_scope'
,
self
.
key
,
set_value
)
# Try to get tag in wrong scope
with
self
.
assertRaises
(
ValueError
):
self
.
runtime
.
service
(
self
.
mock_block
,
'user_tags'
)
.
get_tag
(
'fake_scope'
,
self
.
key
)
lms/templates/seq_module.html
View file @
bce7d9e4
...
...
@@ -19,6 +19,7 @@
data-element=
"${idx+1}"
href=
"javascript:void(0);"
title=
"${item['title']|h}"
data-page-title=
"${item['page_title']|h}"
aria-controls=
"seq_contents_${idx}"
id=
"tab_${idx}"
tabindex=
"0"
...
...
@@ -36,7 +37,7 @@
</nav>
% for idx, item in enumerate(items):
<div
id=
"seq_contents_${idx}"
<div
id=
"seq_contents_${idx}"
aria-labelledby=
"tab_${idx}"
aria-hidden=
"true"
class=
"seq_contents tex2jax_ignore asciimath2jax_ignore"
>
...
...
lms/templates/split_test_staff_view.html
View file @
bce7d9e4
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
%
>
<div
class=
"split-test-view"
>
<select
class=
"split-test-select"
>
% for idx, item in enumerate(items):
<option
value=
"${item['group_id']}"
>
Group ${item['group_id']}
</option>
## Translators: The 'Group' here refers to the group of users that has been sorted into group_id
<option
value=
"${item['group_id']}"
>
${_("Group {group_id}").format(group_id=item['group_id'])}
</option>
%endfor
</select>
% for idx, item in enumerate(items):
<div
class=
"split-test-child"
data-group-id=
"${item['group_id']}"
data-id=
"${item['id']}"
>
${item['content']}
<div
class=
"split-test-child"
data-group-id=
"${item['group_id']}"
data-id=
"${item['id']}"
>
${item['content']
| h
}
</div>
% endfor
<div
class=
'split-test-child-container'
></div>
</div>
requirements/edx/github.txt
View file @
bce7d9e4
...
...
@@ -17,7 +17,7 @@
-e git+https://github.com/appliedsec/pygeoip.git@95e69341cebf5a6a9fbf7c4f5439d458898bdc3b#egg=pygeoip
# Our libraries:
-e git+https://github.com/edx/XBlock.git@
893cd83dfb24405ce81b07f49c1c2e3053cdc865
#egg=XBlock
-e git+https://github.com/edx/XBlock.git@
6dd8a9223cae34184ba5e2e1a186f36c4df1e080
#egg=XBlock
-e git+https://github.com/edx/codejail.git@e3d98f9455#egg=codejail
-e git+https://github.com/edx/diff-cover.git@v0.2.9#egg=diff_cover
-e git+https://github.com/edx/js-test-tool.git@v0.1.5#egg=js_test_tool
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment