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
ccf20e52
Commit
ccf20e52
authored
Jun 29, 2015
by
Ben McMorran
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
TNL-2458 Cache discussion id mapping on course publish
parent
37a0c19a
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
307 additions
and
31 deletions
+307
-31
lms/djangoapps/django_comment_client/base/views.py
+4
-2
lms/djangoapps/django_comment_client/tests/test_utils.py
+89
-0
lms/djangoapps/django_comment_client/utils.py
+61
-14
openedx/core/djangoapps/content/course_structures/migrations/0002_auto__add_field_coursestructure_discussion_id_map_json.py
+35
-0
openedx/core/djangoapps/content/course_structures/models.py
+17
-1
openedx/core/djangoapps/content/course_structures/signals.py
+10
-0
openedx/core/djangoapps/content/course_structures/tasks.py
+21
-7
openedx/core/djangoapps/content/course_structures/tests.py
+70
-7
No files found.
lms/djangoapps/django_comment_client/base/views.py
View file @
ccf20e52
...
...
@@ -29,6 +29,7 @@ from django_comment_client.utils import (
get_group_id_for_comments_service
,
get_discussion_categories_ids
,
get_discussion_id_map
,
get_cached_discussion_id_map
,
)
from
django_comment_client.permissions
import
check_permissions_by_view
,
has_permission
from
eventtracking
import
tracker
...
...
@@ -78,10 +79,11 @@ def track_forum_event(request, event_name, course, obj, data, id_map=None):
"""
user
=
request
.
user
data
[
'id'
]
=
obj
.
id
commentable_id
=
data
[
'commentable_id'
]
if
id_map
is
None
:
id_map
=
get_
discussion_id_map
(
course
,
user
)
id_map
=
get_
cached_discussion_id_map
(
course
,
commentable_id
,
user
)
commentable_id
=
data
[
'commentable_id'
]
if
commentable_id
in
id_map
:
data
[
'category_name'
]
=
id_map
[
commentable_id
][
"title"
]
data
[
'category_id'
]
=
commentable_id
...
...
lms/djangoapps/django_comment_client/tests/test_utils.py
View file @
ccf20e52
...
...
@@ -18,6 +18,7 @@ from courseware.tests.factories import InstructorFactory
from
courseware.tabs
import
get_course_tab_list
from
openedx.core.djangoapps.course_groups.cohorts
import
set_course_cohort_settings
from
student.tests.factories
import
UserFactory
,
AdminFactory
,
CourseEnrollmentFactory
from
openedx.core.djangoapps.content.course_structures.models
import
CourseStructure
from
openedx.core.djangoapps.util.testing
import
ContentGroupTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
...
...
@@ -154,6 +155,94 @@ class CoursewareContextTestCase(ModuleStoreTestCase):
assertThreadCorrect
(
threads
[
1
],
self
.
discussion2
,
"Subsection / Discussion 2"
)
class
CachedDiscussionIdMapTestCase
(
ModuleStoreTestCase
):
"""
Tests that using the cache of discussion id mappings has the same behavior as searching through the course.
"""
def
setUp
(
self
):
super
(
CachedDiscussionIdMapTestCase
,
self
)
.
setUp
(
create_user
=
True
)
self
.
course
=
CourseFactory
.
create
(
org
=
'TestX'
,
number
=
'101'
,
display_name
=
'Test Course'
)
self
.
discussion
=
ItemFactory
.
create
(
parent_location
=
self
.
course
.
location
,
category
=
'discussion'
,
discussion_id
=
'test_discussion_id'
,
discussion_category
=
'Chapter'
,
discussion_target
=
'Discussion 1'
)
self
.
private_discussion
=
ItemFactory
.
create
(
parent_location
=
self
.
course
.
location
,
category
=
'discussion'
,
discussion_id
=
'private_discussion_id'
,
discussion_category
=
'Chapter 3'
,
discussion_target
=
'Beta Testing'
,
visible_to_staff_only
=
True
)
self
.
bad_discussion
=
ItemFactory
.
create
(
parent_location
=
self
.
course
.
location
,
category
=
'discussion'
,
discussion_id
=
'bad_discussion_id'
,
discussion_category
=
None
,
discussion_target
=
None
)
def
test_cache_returns_correct_key
(
self
):
usage_key
=
utils
.
get_cached_discussion_key
(
self
.
course
,
'test_discussion_id'
)
self
.
assertEqual
(
usage_key
,
self
.
discussion
.
location
)
def
test_cache_returns_none_if_id_is_not_present
(
self
):
usage_key
=
utils
.
get_cached_discussion_key
(
self
.
course
,
'bogus_id'
)
self
.
assertIsNone
(
usage_key
)
def
test_cache_raises_exception_if_course_structure_not_cached
(
self
):
CourseStructure
.
objects
.
all
()
.
delete
()
with
self
.
assertRaises
(
utils
.
DiscussionIdMapIsNotCached
):
utils
.
get_cached_discussion_key
(
self
.
course
,
'test_discussion_id'
)
def
test_cache_raises_exception_if_discussion_id_not_cached
(
self
):
cache
=
CourseStructure
.
objects
.
get
(
course_id
=
self
.
course
.
id
)
cache
.
discussion_id_map_json
=
None
cache
.
save
()
with
self
.
assertRaises
(
utils
.
DiscussionIdMapIsNotCached
):
utils
.
get_cached_discussion_key
(
self
.
course
,
'test_discussion_id'
)
def
test_module_does_not_have_required_keys
(
self
):
self
.
assertTrue
(
utils
.
has_required_keys
(
self
.
discussion
))
self
.
assertFalse
(
utils
.
has_required_keys
(
self
.
bad_discussion
))
def
verify_discussion_metadata
(
self
):
"""Retrieves the metadata for self.discussion and verifies that it is correct"""
metadata
=
utils
.
get_cached_discussion_id_map
(
self
.
course
,
'test_discussion_id'
,
self
.
user
)
metadata
=
metadata
[
self
.
discussion
.
discussion_id
]
self
.
assertEqual
(
metadata
[
'location'
],
self
.
discussion
.
location
)
self
.
assertEqual
(
metadata
[
'title'
],
'Chapter / Discussion 1'
)
def
test_get_discussion_id_map_from_cache
(
self
):
self
.
verify_discussion_metadata
()
def
test_get_discussion_id_map_without_cache
(
self
):
CourseStructure
.
objects
.
all
()
.
delete
()
self
.
verify_discussion_metadata
()
def
test_get_missing_discussion_id_map_from_cache
(
self
):
metadata
=
utils
.
get_cached_discussion_id_map
(
self
.
course
,
'bogus_id'
,
self
.
user
)
self
.
assertEqual
(
metadata
,
{})
def
test_get_discussion_id_map_from_cache_without_access
(
self
):
user
=
UserFactory
.
create
()
metadata
=
utils
.
get_cached_discussion_id_map
(
self
.
course
,
'private_discussion_id'
,
self
.
user
)
self
.
assertEqual
(
metadata
[
'private_discussion_id'
][
'title'
],
'Chapter 3 / Beta Testing'
)
metadata
=
utils
.
get_cached_discussion_id_map
(
self
.
course
,
'private_discussion_id'
,
user
)
self
.
assertEqual
(
metadata
,
{})
def
test_get_bad_discussion_id
(
self
):
metadata
=
utils
.
get_cached_discussion_id_map
(
self
.
course
,
'bad_discussion_id'
,
self
.
user
)
self
.
assertEqual
(
metadata
,
{})
class
CategoryMapTestMixin
(
object
):
"""
Provides functionality for classes that test
...
...
lms/djangoapps/django_comment_client/utils.py
View file @
ccf20e52
...
...
@@ -20,6 +20,7 @@ from django_comment_client.settings import MAX_COMMENT_DEPTH
from
edxmako
import
lookup_template
from
courseware.access
import
has_access
from
openedx.core.djangoapps.content.course_structures.models
import
CourseStructure
from
openedx.core.djangoapps.course_groups.cohorts
import
(
get_course_cohort_settings
,
get_cohort_by_id
,
get_cohort_id
,
is_commentable_cohorted
,
is_course_cohorted
)
...
...
@@ -62,6 +63,15 @@ def has_forum_access(uname, course_id, rolename):
return
role
.
users
.
filter
(
username
=
uname
)
.
exists
()
def
has_required_keys
(
module
):
"""Returns True iff module has the proper attributes for generating metadata with get_discussion_id_map_entry()"""
for
key
in
(
'discussion_id'
,
'discussion_category'
,
'discussion_target'
):
if
getattr
(
module
,
key
,
None
)
is
None
:
log
.
debug
(
"Required key '
%
s' not in discussion
%
s, leaving out of category map"
,
key
,
module
.
location
)
return
False
return
True
def
get_accessible_discussion_modules
(
course
,
user
,
include_all
=
False
):
# pylint: disable=invalid-name
"""
Return a list of all valid discussion modules in this course that
...
...
@@ -69,31 +79,68 @@ def get_accessible_discussion_modules(course, user, include_all=False): # pylin
"""
all_modules
=
modulestore
()
.
get_items
(
course
.
id
,
qualifiers
=
{
'category'
:
'discussion'
})
def
has_required_keys
(
module
):
for
key
in
(
'discussion_id'
,
'discussion_category'
,
'discussion_target'
):
if
getattr
(
module
,
key
,
None
)
is
None
:
log
.
warning
(
"Required key '
%
s' not in discussion
%
s, leaving out of category map"
%
(
key
,
module
.
location
))
return
False
return
True
return
[
module
for
module
in
all_modules
if
has_required_keys
(
module
)
and
(
include_all
or
has_access
(
user
,
'load'
,
module
,
course
.
id
))
]
def
get_discussion_id_map_entry
(
module
):
"""
Returns a tuple of (discussion_id, metadata) suitable for inclusion in the results of get_discussion_id_map().
"""
return
(
module
.
discussion_id
,
{
"location"
:
module
.
location
,
"title"
:
module
.
discussion_category
.
split
(
"/"
)[
-
1
]
.
strip
()
+
" / "
+
module
.
discussion_target
}
)
class
DiscussionIdMapIsNotCached
(
Exception
):
"""Thrown when the discussion id map is not cached for this course, but an attempt was made to access it."""
pass
def
get_cached_discussion_key
(
course
,
discussion_id
):
"""
Returns the usage key of the discussion module associated with discussion_id if it is cached. If the discussion id
map is cached but does not contain discussion_id, returns None. If the discussion id map is not cached for course,
raises a DiscussionIdMapIsNotCached exception.
"""
try
:
cached_mapping
=
CourseStructure
.
objects
.
get
(
course_id
=
course
.
id
)
.
discussion_id_map
if
not
cached_mapping
:
raise
DiscussionIdMapIsNotCached
()
return
cached_mapping
.
get
(
discussion_id
)
except
CourseStructure
.
DoesNotExist
:
raise
DiscussionIdMapIsNotCached
()
def
get_cached_discussion_id_map
(
course
,
discussion_id
,
user
):
"""
Returns a dict mapping discussion_id to discussion module metadata if it is cached and visible to the user.
If not, returns the result of get_discussion_id_map
"""
try
:
key
=
get_cached_discussion_key
(
course
,
discussion_id
)
if
not
key
:
return
{}
module
=
modulestore
()
.
get_item
(
key
)
if
not
(
has_required_keys
(
module
)
and
has_access
(
user
,
'load'
,
module
,
course
.
id
)):
return
{}
return
dict
([
get_discussion_id_map_entry
(
module
)])
except
DiscussionIdMapIsNotCached
:
return
get_discussion_id_map
(
course
,
user
)
def
get_discussion_id_map
(
course
,
user
):
"""
Transform the list of this course's discussion modules (visible to a given user) into a dictionary of metadata keyed
by discussion_id.
"""
def
get_entry
(
module
):
# pylint: disable=missing-docstring
discussion_id
=
module
.
discussion_id
title
=
module
.
discussion_target
last_category
=
module
.
discussion_category
.
split
(
"/"
)[
-
1
]
.
strip
()
return
(
discussion_id
,
{
"location"
:
module
.
location
,
"title"
:
last_category
+
" / "
+
title
})
return
dict
(
map
(
get_entry
,
get_accessible_discussion_modules
(
course
,
user
)))
return
dict
(
map
(
get_discussion_id_map_entry
,
get_accessible_discussion_modules
(
course
,
user
)))
def
_filter_unstarted_categories
(
category_map
):
...
...
openedx/core/djangoapps/content/course_structures/migrations/0002_auto__add_field_coursestructure_discussion_id_map_json.py
0 → 100644
View file @
ccf20e52
# -*- coding: utf-8 -*-
from
south.utils
import
datetime_utils
as
datetime
from
south.db
import
db
from
south.v2
import
SchemaMigration
from
django.db
import
models
class
Migration
(
SchemaMigration
):
def
forwards
(
self
,
orm
):
# Adding field 'CourseStructure.discussion_id_map_json'
db
.
add_column
(
'course_structures_coursestructure'
,
'discussion_id_map_json'
,
self
.
gf
(
'django.db.models.fields.TextField'
)(
null
=
True
,
blank
=
True
),
keep_default
=
False
)
def
backwards
(
self
,
orm
):
# Deleting field 'CourseStructure.discussion_id_map_json'
db
.
delete_column
(
'course_structures_coursestructure'
,
'discussion_id_map_json'
)
models
=
{
'course_structures.coursestructure'
:
{
'Meta'
:
{
'object_name'
:
'CourseStructure'
},
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'created'
:
(
'model_utils.fields.AutoCreatedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'discussion_id_map_json'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified'
:
(
'model_utils.fields.AutoLastModifiedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'structure_json'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
})
}
}
complete_apps
=
[
'course_structures'
]
\ No newline at end of file
openedx/core/djangoapps/content/course_structures/models.py
View file @
ccf20e52
...
...
@@ -5,7 +5,7 @@ from collections import OrderedDict
from
model_utils.models
import
TimeStampedModel
from
util.models
import
CompressedTextField
from
xmodule_django.models
import
CourseKeyField
from
xmodule_django.models
import
CourseKeyField
,
UsageKey
logger
=
logging
.
getLogger
(
__name__
)
# pylint: disable=invalid-name
...
...
@@ -21,6 +21,9 @@ class CourseStructure(TimeStampedModel):
# we'd have to be careful about caching.
structure_json
=
CompressedTextField
(
verbose_name
=
'Structure JSON'
,
blank
=
True
,
null
=
True
)
# JSON mapping of discussion ids to usage keys for the corresponding discussion modules
discussion_id_map_json
=
CompressedTextField
(
verbose_name
=
'Discussion ID Map JSON'
,
blank
=
True
,
null
=
True
)
@property
def
structure
(
self
):
if
self
.
structure_json
:
...
...
@@ -37,6 +40,19 @@ class CourseStructure(TimeStampedModel):
self
.
_traverse_tree
(
self
.
structure
[
'root'
],
self
.
structure
[
'blocks'
],
ordered_blocks
)
return
ordered_blocks
@property
def
discussion_id_map
(
self
):
"""
Return a mapping of discussion ids to usage keys of the corresponding discussion modules.
"""
if
self
.
discussion_id_map_json
:
result
=
json
.
loads
(
self
.
discussion_id_map_json
)
for
discussion_id
in
result
:
# Usage key strings might not include the course run, so we add it back in with map_into_course
result
[
discussion_id
]
=
UsageKey
.
from_string
(
result
[
discussion_id
])
.
map_into_course
(
self
.
course_id
)
return
result
return
None
def
_traverse_tree
(
self
,
block
,
unordered_structure
,
ordered_blocks
,
parent
=
None
):
"""
Traverses the tree and fills in the ordered_blocks OrderedDict with the blocks in
...
...
openedx/core/djangoapps/content/course_structures/signals.py
View file @
ccf20e52
...
...
@@ -2,12 +2,22 @@ from django.dispatch.dispatcher import receiver
from
xmodule.modulestore.django
import
SignalHandler
from
.models
import
CourseStructure
@receiver
(
SignalHandler
.
course_published
)
def
listen_for_course_publish
(
sender
,
course_key
,
**
kwargs
):
# pylint: disable=unused-argument
# Import tasks here to avoid a circular import.
from
.tasks
import
update_course_structure
# Delete the existing discussion id map cache to avoid inconsistencies
try
:
structure
=
CourseStructure
.
objects
.
get
(
course_id
=
course_key
)
structure
.
discussion_id_map_json
=
None
structure
.
save
()
except
CourseStructure
.
DoesNotExist
:
pass
# Note: The countdown=0 kwarg is set to to ensure the method below does not attempt to access the course
# before the signal emitter has finished all operations. This is also necessary to ensure all tests pass.
update_course_structure
.
apply_async
([
unicode
(
course_key
)],
countdown
=
0
)
openedx/core/djangoapps/content/course_structures/tasks.py
View file @
ccf20e52
...
...
@@ -17,6 +17,7 @@ def _generate_course_structure(course_key):
course
=
modulestore
()
.
get_course
(
course_key
,
depth
=
None
)
blocks_stack
=
[
course
]
blocks_dict
=
{}
discussions
=
{}
while
blocks_stack
:
curr_block
=
blocks_stack
.
pop
()
children
=
curr_block
.
get_children
()
if
curr_block
.
has_children
else
[]
...
...
@@ -28,6 +29,11 @@ def _generate_course_structure(course_key):
"children"
:
[
unicode
(
child
.
scope_ids
.
usage_id
)
for
child
in
children
]
}
if
(
curr_block
.
category
==
'discussion'
and
hasattr
(
curr_block
,
'discussion_id'
)
and
curr_block
.
discussion_id
):
discussions
[
curr_block
.
discussion_id
]
=
unicode
(
curr_block
.
scope_ids
.
usage_id
)
# Retrieve these attributes separately so that we can fail gracefully
# if the block doesn't have the attribute.
attrs
=
((
'graded'
,
False
),
(
'format'
,
None
))
...
...
@@ -43,8 +49,11 @@ def _generate_course_structure(course_key):
# Add this blocks children to the stack so that we can traverse them as well.
blocks_stack
.
extend
(
children
)
return
{
"root"
:
unicode
(
course
.
scope_ids
.
usage_id
),
"blocks"
:
blocks_dict
'structure'
:
{
"root"
:
unicode
(
course
.
scope_ids
.
usage_id
),
"blocks"
:
blocks_dict
},
'discussion_id_map'
:
discussions
}
...
...
@@ -69,13 +78,18 @@ def update_course_structure(course_key):
log
.
exception
(
'An error occurred while generating course structure:
%
s'
,
ex
.
message
)
raise
structure_json
=
json
.
dumps
(
structure
)
structure_json
=
json
.
dumps
(
structure
[
'structure'
])
discussion_id_map_json
=
json
.
dumps
(
structure
[
'discussion_id_map'
])
cs
,
created
=
CourseStructure
.
objects
.
get_or_create
(
structure_model
,
created
=
CourseStructure
.
objects
.
get_or_create
(
course_id
=
course_key
,
defaults
=
{
'structure_json'
:
structure_json
}
defaults
=
{
'structure_json'
:
structure_json
,
'discussion_id_map_json'
:
discussion_id_map_json
}
)
if
not
created
:
cs
.
structure_json
=
structure_json
cs
.
save
()
structure_model
.
structure_json
=
structure_json
structure_model
.
discussion_id_map_json
=
discussion_id_map_json
structure_model
.
save
()
openedx/core/djangoapps/content/course_structures/tests.py
View file @
ccf20e52
import
json
from
xmodule_django.models
import
UsageKey
from
xmodule.modulestore.django
import
SignalHandler
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
...
...
@@ -21,8 +22,18 @@ class SignalDisconnectTestMixin(object):
class
CourseStructureTaskTests
(
ModuleStoreTestCase
):
def
setUp
(
self
,
**
kwargs
):
super
(
CourseStructureTaskTests
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
()
self
.
course
=
CourseFactory
.
create
(
org
=
'TestX'
,
course
=
'TS101'
,
run
=
'T1'
)
self
.
section
=
ItemFactory
.
create
(
parent
=
self
.
course
,
category
=
'chapter'
,
display_name
=
'Test Section'
)
self
.
discussion_module_1
=
ItemFactory
.
create
(
parent
=
self
.
course
,
category
=
'discussion'
,
discussion_id
=
'test_discussion_id_1'
)
self
.
discussion_module_2
=
ItemFactory
.
create
(
parent
=
self
.
course
,
category
=
'discussion'
,
discussion_id
=
'test_discussion_id_2'
)
CourseStructure
.
objects
.
all
()
.
delete
()
def
test_generate_course_structure
(
self
):
...
...
@@ -52,7 +63,7 @@ class CourseStructureTaskTests(ModuleStoreTestCase):
self
.
maxDiff
=
None
actual
=
_generate_course_structure
(
self
.
course
.
id
)
self
.
assertDictEqual
(
actual
,
expected
)
self
.
assertDictEqual
(
actual
[
'structure'
]
,
expected
)
def
test_structure_json
(
self
):
"""
...
...
@@ -138,7 +149,7 @@ class CourseStructureTaskTests(ModuleStoreTestCase):
structure
=
_generate_course_structure
(
self
.
course
.
id
)
usage_key
=
unicode
(
module
.
location
)
actual
=
structure
[
'blocks'
][
usage_key
]
actual
=
structure
[
'
structure'
][
'
blocks'
][
usage_key
]
expected
=
{
"usage_key"
:
usage_key
,
"block_type"
:
category
,
...
...
@@ -149,6 +160,53 @@ class CourseStructureTaskTests(ModuleStoreTestCase):
}
self
.
assertEqual
(
actual
,
expected
)
def
test_generate_discussion_id_map
(
self
):
id_map
=
{}
def
add_block
(
block
):
"""Adds the given block and all of its children to the expected discussion id map"""
children
=
block
.
get_children
()
if
block
.
has_children
else
[]
if
block
.
category
==
'discussion'
:
id_map
[
block
.
discussion_id
]
=
unicode
(
block
.
location
)
for
child
in
children
:
add_block
(
child
)
add_block
(
self
.
course
)
actual
=
_generate_course_structure
(
self
.
course
.
id
)
self
.
assertEqual
(
actual
[
'discussion_id_map'
],
id_map
)
def
test_discussion_id_map_json
(
self
):
id_map
=
{
'discussion_id_1'
:
'module_location_1'
,
'discussion_id_2'
:
'module_location_2'
}
id_map_json
=
json
.
dumps
(
id_map
)
structure
=
CourseStructure
.
objects
.
create
(
course_id
=
self
.
course
.
id
,
discussion_id_map_json
=
id_map_json
)
self
.
assertEqual
(
structure
.
discussion_id_map_json
,
id_map_json
)
structure
=
CourseStructure
.
objects
.
get
(
course_id
=
self
.
course
.
id
)
self
.
assertEqual
(
structure
.
discussion_id_map_json
,
id_map_json
)
def
test_discussion_id_map
(
self
):
id_map
=
{
'discussion_id_1'
:
'block-v1:TestX+TS101+T1+type@discussion+block@b141953dff414921a715da37eb14ecdc'
,
'discussion_id_2'
:
'i4x://TestX/TS101/discussion/466f474fa4d045a8b7bde1b911e095ca'
}
id_map_json
=
json
.
dumps
(
id_map
)
structure
=
CourseStructure
.
objects
.
create
(
course_id
=
self
.
course
.
id
,
discussion_id_map_json
=
id_map_json
)
expected_id_map
=
{
key
:
UsageKey
.
from_string
(
value
)
.
map_into_course
(
self
.
course
.
id
)
for
key
,
value
in
id_map
.
iteritems
()
}
self
.
assertEqual
(
structure
.
discussion_id_map
,
expected_id_map
)
def
test_discussion_id_map_missing
(
self
):
structure
=
CourseStructure
.
objects
.
create
(
course_id
=
self
.
course
.
id
)
self
.
assertIsNone
(
structure
.
discussion_id_map
)
def
test_update_course_structure
(
self
):
"""
Test the actual task that orchestrates data generation and updating the database.
...
...
@@ -158,8 +216,13 @@ class CourseStructureTaskTests(ModuleStoreTestCase):
self
.
assertRaises
(
ValueError
,
update_course_structure
,
course_id
)
# Ensure a CourseStructure object is created
structure
=
_generate_course_structure
(
course_id
)
expected_
structure
=
_generate_course_structure
(
course_id
)
update_course_structure
(
unicode
(
course_id
))
cs
=
CourseStructure
.
objects
.
get
(
course_id
=
course_id
)
self
.
assertEqual
(
cs
.
course_id
,
course_id
)
self
.
assertEqual
(
cs
.
structure
,
structure
)
structure
=
CourseStructure
.
objects
.
get
(
course_id
=
course_id
)
self
.
assertEqual
(
structure
.
course_id
,
course_id
)
self
.
assertEqual
(
structure
.
structure
,
expected_structure
[
'structure'
])
self
.
assertEqual
(
structure
.
discussion_id_map
.
keys
(),
expected_structure
[
'discussion_id_map'
]
.
keys
())
self
.
assertEqual
(
[
unicode
(
value
)
for
value
in
structure
.
discussion_id_map
.
values
()],
expected_structure
[
'discussion_id_map'
]
.
values
()
)
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