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
5fb2a1fd
Commit
5fb2a1fd
authored
Jun 19, 2015
by
Diana Huang
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #8583 from edx/diana/course-tab-cleanup
Make course tab refreshing safer.
parents
c59a3db7
d779466f
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
67 additions
and
23 deletions
+67
-23
cms/djangoapps/contentstore/tests/test_course_settings.py
+36
-19
cms/djangoapps/contentstore/views/course.py
+22
-4
lms/djangoapps/course_wiki/tab.py
+1
-0
lms/djangoapps/courseware/tabs.py
+6
-0
lms/djangoapps/courseware/tests/test_tabs.py
+1
-0
lms/djangoapps/django_comment_client/forum/views.py
+1
-0
No files found.
cms/djangoapps/contentstore/tests/test_course_settings.py
View file @
5fb2a1fd
...
@@ -19,6 +19,7 @@ from xmodule.modulestore.tests.factories import CourseFactory
...
@@ -19,6 +19,7 @@ from xmodule.modulestore.tests.factories import CourseFactory
from
models.settings.course_metadata
import
CourseMetadata
from
models.settings.course_metadata
import
CourseMetadata
from
xmodule.fields
import
Date
from
xmodule.fields
import
Date
from
xmodule.tabs
import
InvalidTabsException
from
.utils
import
CourseTestCase
from
.utils
import
CourseTestCase
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
...
@@ -617,6 +618,7 @@ class CourseGradingTest(CourseTestCase):
...
@@ -617,6 +618,7 @@ class CourseGradingTest(CourseTestCase):
self
.
assertEqual
(
json
.
loads
(
response
.
content
)
.
get
(
'graderType'
),
u'notgraded'
)
self
.
assertEqual
(
json
.
loads
(
response
.
content
)
.
get
(
'graderType'
),
u'notgraded'
)
@ddt.ddt
class
CourseMetadataEditingTest
(
CourseTestCase
):
class
CourseMetadataEditingTest
(
CourseTestCase
):
"""
"""
Tests for CourseMetadata.
Tests for CourseMetadata.
...
@@ -626,6 +628,7 @@ class CourseMetadataEditingTest(CourseTestCase):
...
@@ -626,6 +628,7 @@ class CourseMetadataEditingTest(CourseTestCase):
self
.
fullcourse
=
CourseFactory
.
create
()
self
.
fullcourse
=
CourseFactory
.
create
()
self
.
course_setting_url
=
get_url
(
self
.
course
.
id
,
'advanced_settings_handler'
)
self
.
course_setting_url
=
get_url
(
self
.
course
.
id
,
'advanced_settings_handler'
)
self
.
fullcourse_setting_url
=
get_url
(
self
.
fullcourse
.
id
,
'advanced_settings_handler'
)
self
.
fullcourse_setting_url
=
get_url
(
self
.
fullcourse
.
id
,
'advanced_settings_handler'
)
self
.
notes_tab
=
{
"type"
:
"notes"
,
"name"
:
"My Notes"
}
def
test_fetch_initial_fields
(
self
):
def
test_fetch_initial_fields
(
self
):
test_model
=
CourseMetadata
.
fetch
(
self
.
course
)
test_model
=
CourseMetadata
.
fetch
(
self
.
course
)
...
@@ -930,12 +933,11 @@ class CourseMetadataEditingTest(CourseTestCase):
...
@@ -930,12 +933,11 @@ class CourseMetadataEditingTest(CourseTestCase):
"""
"""
open_ended_tab
=
{
"type"
:
"open_ended"
,
"name"
:
"Open Ended Panel"
}
open_ended_tab
=
{
"type"
:
"open_ended"
,
"name"
:
"Open Ended Panel"
}
peer_grading_tab
=
{
"type"
:
"peer_grading"
,
"name"
:
"Peer grading"
}
peer_grading_tab
=
{
"type"
:
"peer_grading"
,
"name"
:
"Peer grading"
}
notes_tab
=
{
"type"
:
"notes"
,
"name"
:
"My Notes"
}
# First ensure that none of the tabs are visible
# First ensure that none of the tabs are visible
self
.
assertNotIn
(
open_ended_tab
,
self
.
course
.
tabs
)
self
.
assertNotIn
(
open_ended_tab
,
self
.
course
.
tabs
)
self
.
assertNotIn
(
peer_grading_tab
,
self
.
course
.
tabs
)
self
.
assertNotIn
(
peer_grading_tab
,
self
.
course
.
tabs
)
self
.
assertNotIn
(
notes_tab
,
self
.
course
.
tabs
)
self
.
assertNotIn
(
self
.
notes_tab
,
self
.
course
.
tabs
)
# Now add the "combinedopenended" component and verify that the tab has been added
# Now add the "combinedopenended" component and verify that the tab has been added
self
.
client
.
ajax_post
(
self
.
course_setting_url
,
{
self
.
client
.
ajax_post
(
self
.
course_setting_url
,
{
...
@@ -944,7 +946,7 @@ class CourseMetadataEditingTest(CourseTestCase):
...
@@ -944,7 +946,7 @@ class CourseMetadataEditingTest(CourseTestCase):
course
=
modulestore
()
.
get_course
(
self
.
course
.
id
)
course
=
modulestore
()
.
get_course
(
self
.
course
.
id
)
self
.
assertIn
(
open_ended_tab
,
course
.
tabs
)
self
.
assertIn
(
open_ended_tab
,
course
.
tabs
)
self
.
assertIn
(
peer_grading_tab
,
course
.
tabs
)
self
.
assertIn
(
peer_grading_tab
,
course
.
tabs
)
self
.
assertNotIn
(
notes_tab
,
course
.
tabs
)
self
.
assertNotIn
(
self
.
notes_tab
,
course
.
tabs
)
# Now enable student notes and verify that the "My Notes" tab has also been added
# Now enable student notes and verify that the "My Notes" tab has also been added
self
.
client
.
ajax_post
(
self
.
course_setting_url
,
{
self
.
client
.
ajax_post
(
self
.
course_setting_url
,
{
...
@@ -953,7 +955,7 @@ class CourseMetadataEditingTest(CourseTestCase):
...
@@ -953,7 +955,7 @@ class CourseMetadataEditingTest(CourseTestCase):
course
=
modulestore
()
.
get_course
(
self
.
course
.
id
)
course
=
modulestore
()
.
get_course
(
self
.
course
.
id
)
self
.
assertIn
(
open_ended_tab
,
course
.
tabs
)
self
.
assertIn
(
open_ended_tab
,
course
.
tabs
)
self
.
assertIn
(
peer_grading_tab
,
course
.
tabs
)
self
.
assertIn
(
peer_grading_tab
,
course
.
tabs
)
self
.
assertIn
(
notes_tab
,
course
.
tabs
)
self
.
assertIn
(
self
.
notes_tab
,
course
.
tabs
)
# Now remove the "combinedopenended" component and verify that the tab is gone
# Now remove the "combinedopenended" component and verify that the tab is gone
self
.
client
.
ajax_post
(
self
.
course_setting_url
,
{
self
.
client
.
ajax_post
(
self
.
course_setting_url
,
{
...
@@ -962,7 +964,7 @@ class CourseMetadataEditingTest(CourseTestCase):
...
@@ -962,7 +964,7 @@ class CourseMetadataEditingTest(CourseTestCase):
course
=
modulestore
()
.
get_course
(
self
.
course
.
id
)
course
=
modulestore
()
.
get_course
(
self
.
course
.
id
)
self
.
assertNotIn
(
open_ended_tab
,
course
.
tabs
)
self
.
assertNotIn
(
open_ended_tab
,
course
.
tabs
)
self
.
assertNotIn
(
peer_grading_tab
,
course
.
tabs
)
self
.
assertNotIn
(
peer_grading_tab
,
course
.
tabs
)
self
.
assertIn
(
notes_tab
,
course
.
tabs
)
self
.
assertIn
(
self
.
notes_tab
,
course
.
tabs
)
# Finally disable student notes and verify that the "My Notes" tab is gone
# Finally disable student notes and verify that the "My Notes" tab is gone
self
.
client
.
ajax_post
(
self
.
course_setting_url
,
{
self
.
client
.
ajax_post
(
self
.
course_setting_url
,
{
...
@@ -971,25 +973,40 @@ class CourseMetadataEditingTest(CourseTestCase):
...
@@ -971,25 +973,40 @@ class CourseMetadataEditingTest(CourseTestCase):
course
=
modulestore
()
.
get_course
(
self
.
course
.
id
)
course
=
modulestore
()
.
get_course
(
self
.
course
.
id
)
self
.
assertNotIn
(
open_ended_tab
,
course
.
tabs
)
self
.
assertNotIn
(
open_ended_tab
,
course
.
tabs
)
self
.
assertNotIn
(
peer_grading_tab
,
course
.
tabs
)
self
.
assertNotIn
(
peer_grading_tab
,
course
.
tabs
)
self
.
assertNotIn
(
notes_tab
,
course
.
tabs
)
self
.
assertNotIn
(
self
.
notes_tab
,
course
.
tabs
)
def
mark_wiki_as_hidden
(
self
,
tabs
):
def
test_advanced_components_munge_tabs_validation_failure
(
self
):
""" Mark the wiki tab as hidden. """
with
patch
(
'contentstore.views.course._refresh_course_tabs'
,
side_effect
=
InvalidTabsException
):
for
tab
in
tabs
:
resp
=
self
.
client
.
ajax_post
(
self
.
course_setting_url
,
{
if
tab
.
type
==
'wiki'
:
ADVANCED_COMPONENT_POLICY_KEY
:
{
"value"
:
[
"notes"
]}
tab
[
'is_hidden'
]
=
True
})
return
tabs
self
.
assertEqual
(
resp
.
status_code
,
400
)
def
test_advanced_components_munge_tabs_hidden_tabs
(
self
):
error_msg
=
[
updated_tabs
=
self
.
mark_wiki_as_hidden
(
self
.
course
.
tabs
)
{
self
.
course
.
tabs
=
updated_tabs
'message'
:
'An error occurred while trying to save your tabs'
,
'model'
:
{
'display_name'
:
'Tabs Exception'
}
}
]
self
.
assertEqual
(
json
.
loads
(
resp
.
content
),
error_msg
)
# verify that the course wasn't saved into the modulestore
course
=
modulestore
()
.
get_course
(
self
.
course
.
id
)
self
.
assertNotIn
(
"notes"
,
course
.
advanced_modules
)
@ddt.data
(
[{
'type'
:
'courseware'
},
{
'type'
:
'course_info'
},
{
'type'
:
'wiki'
,
'is_hidden'
:
True
}],
[{
'type'
:
'courseware'
,
'name'
:
'Courses'
},
{
'type'
:
'course_info'
,
'name'
:
'Info'
}],
)
def
test_course_tab_configurations
(
self
,
tab_list
):
self
.
course
.
tabs
=
tab_list
modulestore
()
.
update_item
(
self
.
course
,
self
.
user
.
id
)
modulestore
()
.
update_item
(
self
.
course
,
self
.
user
.
id
)
self
.
client
.
ajax_post
(
self
.
course_setting_url
,
{
self
.
client
.
ajax_post
(
self
.
course_setting_url
,
{
ADVANCED_COMPONENT_POLICY_KEY
:
{
"value"
:
[
"notes"
]}
ADVANCED_COMPONENT_POLICY_KEY
:
{
"value"
:
[
"notes"
]}
})
})
course
=
modulestore
()
.
get_course
(
self
.
course
.
id
)
course
=
modulestore
()
.
get_course
(
self
.
course
.
id
)
notes_tab
=
{
"type"
:
"notes"
,
"name"
:
"My Notes"
}
tab_list
.
append
(
self
.
notes_tab
)
self
.
assert
In
(
notes_tab
,
course
.
tabs
)
self
.
assert
Equal
(
tab_list
,
course
.
tabs
)
class
CourseGraderUpdatesTest
(
CourseTestCase
):
class
CourseGraderUpdatesTest
(
CourseTestCase
):
...
...
cms/djangoapps/contentstore/views/course.py
View file @
5fb2a1fd
...
@@ -5,6 +5,7 @@ import copy
...
@@ -5,6 +5,7 @@ import copy
from
django.shortcuts
import
redirect
from
django.shortcuts
import
redirect
import
json
import
json
import
random
import
random
import
logging
import
string
# pylint: disable=deprecated-module
import
string
# pylint: disable=deprecated-module
from
django.utils.translation
import
ugettext
as
_
from
django.utils.translation
import
ugettext
as
_
import
django.utils
import
django.utils
...
@@ -22,7 +23,7 @@ from xmodule.course_module import DEFAULT_START_DATE
...
@@ -22,7 +23,7 @@ from xmodule.course_module import DEFAULT_START_DATE
from
xmodule.error_module
import
ErrorDescriptor
from
xmodule.error_module
import
ErrorDescriptor
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.contentstore.content
import
StaticContent
from
xmodule.contentstore.content
import
StaticContent
from
xmodule.tabs
import
CourseTab
from
xmodule.tabs
import
CourseTab
,
CourseTabList
,
InvalidTabsException
from
openedx.core.lib.course_tabs
import
CourseTabPluginManager
from
openedx.core.lib.course_tabs
import
CourseTabPluginManager
from
openedx.core.djangoapps.credit.api
import
is_credit_course
,
get_credit_requirements
from
openedx.core.djangoapps.credit.api
import
is_credit_course
,
get_credit_requirements
from
openedx.core.djangoapps.credit.tasks
import
update_credit_course_requirements
from
openedx.core.djangoapps.credit.tasks
import
update_credit_course_requirements
...
@@ -87,6 +88,8 @@ from util.milestones_helpers import (
...
@@ -87,6 +88,8 @@ from util.milestones_helpers import (
is_valid_course_key
is_valid_course_key
)
)
log
=
logging
.
getLogger
(
__name__
)
__all__
=
[
'course_info_handler'
,
'course_handler'
,
'course_listing'
,
__all__
=
[
'course_info_handler'
,
'course_handler'
,
'course_listing'
,
'course_info_update_handler'
,
'course_search_index_handler'
,
'course_info_update_handler'
,
'course_search_index_handler'
,
'course_rerun_handler'
,
'course_rerun_handler'
,
...
@@ -1024,6 +1027,9 @@ def grading_handler(request, course_key_string, grader_index=None):
...
@@ -1024,6 +1027,9 @@ def grading_handler(request, course_key_string, grader_index=None):
def
_refresh_course_tabs
(
request
,
course_module
):
def
_refresh_course_tabs
(
request
,
course_module
):
"""
"""
Automatically adds/removes tabs if changes to the course require them.
Automatically adds/removes tabs if changes to the course require them.
Raises:
InvalidTabsException: raised if there's a problem with the new version of the tabs.
"""
"""
def
update_tab
(
tabs
,
tab_type
,
tab_enabled
):
def
update_tab
(
tabs
,
tab_type
,
tab_enabled
):
...
@@ -1047,6 +1053,8 @@ def _refresh_course_tabs(request, course_module):
...
@@ -1047,6 +1053,8 @@ def _refresh_course_tabs(request, course_module):
tab_enabled
=
tab_type
.
is_enabled
(
course_module
,
user
=
request
.
user
)
tab_enabled
=
tab_type
.
is_enabled
(
course_module
,
user
=
request
.
user
)
update_tab
(
course_tabs
,
tab_type
,
tab_enabled
)
update_tab
(
course_tabs
,
tab_type
,
tab_enabled
)
CourseTabList
.
validate_tabs
(
course_tabs
)
# Save the tabs into the course if they have been changed
# Save the tabs into the course if they have been changed
if
course_tabs
!=
course_module
.
tabs
:
if
course_tabs
!=
course_module
.
tabs
:
course_module
.
tabs
=
course_tabs
course_module
.
tabs
=
course_tabs
...
@@ -1090,8 +1098,18 @@ def advanced_settings_handler(request, course_key_string):
...
@@ -1090,8 +1098,18 @@ def advanced_settings_handler(request, course_key_string):
)
)
if
is_valid
:
if
is_valid
:
# update the course tabs if required by any setting changes
try
:
_refresh_course_tabs
(
request
,
course_module
)
# update the course tabs if required by any setting changes
_refresh_course_tabs
(
request
,
course_module
)
except
InvalidTabsException
as
err
:
log
.
exception
(
err
.
message
)
response_message
=
[
{
'message'
:
_
(
'An error occurred while trying to save your tabs'
),
'model'
:
{
'display_name'
:
_
(
'Tabs Exception'
)}
}
]
return
JsonResponseBadRequest
(
response_message
)
# now update mongo
# now update mongo
modulestore
()
.
update_item
(
course_module
,
request
.
user
.
id
)
modulestore
()
.
update_item
(
course_module
,
request
.
user
.
id
)
...
@@ -1101,7 +1119,7 @@ def advanced_settings_handler(request, course_key_string):
...
@@ -1101,7 +1119,7 @@ def advanced_settings_handler(request, course_key_string):
return
JsonResponseBadRequest
(
errors
)
return
JsonResponseBadRequest
(
errors
)
# Handle all errors that validation doesn't catch
# Handle all errors that validation doesn't catch
except
(
TypeError
,
ValueError
)
as
err
:
except
(
TypeError
,
ValueError
,
InvalidTabsException
)
as
err
:
return
HttpResponseBadRequest
(
return
HttpResponseBadRequest
(
django
.
utils
.
html
.
escape
(
err
.
message
),
django
.
utils
.
html
.
escape
(
err
.
message
),
content_type
=
"text/plain"
content_type
=
"text/plain"
...
...
lms/djangoapps/course_wiki/tab.py
View file @
5fb2a1fd
...
@@ -18,6 +18,7 @@ class WikiTab(EnrolledTab):
...
@@ -18,6 +18,7 @@ class WikiTab(EnrolledTab):
title
=
_
(
'Wiki'
)
title
=
_
(
'Wiki'
)
view_name
=
"course_wiki"
view_name
=
"course_wiki"
is_hideable
=
True
is_hideable
=
True
is_default
=
False
@classmethod
@classmethod
def
is_enabled
(
cls
,
course
,
user
=
None
):
def
is_enabled
(
cls
,
course
,
user
=
None
):
...
...
lms/djangoapps/courseware/tabs.py
View file @
5fb2a1fd
...
@@ -32,6 +32,7 @@ class CoursewareTab(EnrolledTab):
...
@@ -32,6 +32,7 @@ class CoursewareTab(EnrolledTab):
priority
=
10
priority
=
10
view_name
=
'courseware'
view_name
=
'courseware'
is_movable
=
False
is_movable
=
False
is_default
=
False
class
CourseInfoTab
(
CourseTab
):
class
CourseInfoTab
(
CourseTab
):
...
@@ -44,6 +45,7 @@ class CourseInfoTab(CourseTab):
...
@@ -44,6 +45,7 @@ class CourseInfoTab(CourseTab):
view_name
=
'info'
view_name
=
'info'
tab_id
=
'info'
tab_id
=
'info'
is_movable
=
False
is_movable
=
False
is_default
=
False
@classmethod
@classmethod
def
is_enabled
(
cls
,
course
,
user
=
None
):
def
is_enabled
(
cls
,
course
,
user
=
None
):
...
@@ -59,6 +61,7 @@ class SyllabusTab(EnrolledTab):
...
@@ -59,6 +61,7 @@ class SyllabusTab(EnrolledTab):
priority
=
30
priority
=
30
view_name
=
'syllabus'
view_name
=
'syllabus'
allow_multiple
=
True
allow_multiple
=
True
is_default
=
False
@classmethod
@classmethod
def
is_enabled
(
cls
,
course
,
user
=
None
):
# pylint: disable=unused-argument
def
is_enabled
(
cls
,
course
,
user
=
None
):
# pylint: disable=unused-argument
...
@@ -76,6 +79,7 @@ class ProgressTab(EnrolledTab):
...
@@ -76,6 +79,7 @@ class ProgressTab(EnrolledTab):
priority
=
40
priority
=
40
view_name
=
'progress'
view_name
=
'progress'
is_hideable
=
True
is_hideable
=
True
is_default
=
False
@classmethod
@classmethod
def
is_enabled
(
cls
,
course
,
user
=
None
):
# pylint: disable=unused-argument
def
is_enabled
(
cls
,
course
,
user
=
None
):
# pylint: disable=unused-argument
...
@@ -91,6 +95,7 @@ class TextbookTabsBase(CourseTab):
...
@@ -91,6 +95,7 @@ class TextbookTabsBase(CourseTab):
# Translators: 'Textbooks' refers to the tab in the course that leads to the course' textbooks
# Translators: 'Textbooks' refers to the tab in the course that leads to the course' textbooks
title
=
_
(
"Textbooks"
)
title
=
_
(
"Textbooks"
)
is_collection
=
True
is_collection
=
True
is_default
=
False
@classmethod
@classmethod
def
is_enabled
(
cls
,
course
,
user
=
None
):
# pylint: disable=unused-argument
def
is_enabled
(
cls
,
course
,
user
=
None
):
# pylint: disable=unused-argument
...
@@ -222,6 +227,7 @@ class ExternalDiscussionCourseTab(LinkTab):
...
@@ -222,6 +227,7 @@ class ExternalDiscussionCourseTab(LinkTab):
# Translators: 'Discussion' refers to the tab in the courseware that leads to the discussion forums
# Translators: 'Discussion' refers to the tab in the courseware that leads to the discussion forums
title
=
_
(
'Discussion'
)
title
=
_
(
'Discussion'
)
priority
=
None
priority
=
None
is_default
=
False
@classmethod
@classmethod
def
validate
(
cls
,
tab_dict
,
raise_error
=
True
):
def
validate
(
cls
,
tab_dict
,
raise_error
=
True
):
...
...
lms/djangoapps/courseware/tests/test_tabs.py
View file @
5fb2a1fd
...
@@ -480,6 +480,7 @@ class TabListTestCase(TabTestCase):
...
@@ -480,6 +480,7 @@ class TabListTestCase(TabTestCase):
[{
'type'
:
CoursewareTab
.
type
},
{
'type'
:
'discussion'
,
'name'
:
'fake_name'
}],
[{
'type'
:
CoursewareTab
.
type
},
{
'type'
:
'discussion'
,
'name'
:
'fake_name'
}],
# incorrect order
# incorrect order
[{
'type'
:
CourseInfoTab
.
type
,
'name'
:
'fake_name'
},
{
'type'
:
CoursewareTab
.
type
}],
[{
'type'
:
CourseInfoTab
.
type
,
'name'
:
'fake_name'
},
{
'type'
:
CoursewareTab
.
type
}],
[{
'type'
:
'unknown_type'
}]
]
]
# tab types that should appear only once
# tab types that should appear only once
...
...
lms/djangoapps/django_comment_client/forum/views.py
View file @
5fb2a1fd
...
@@ -59,6 +59,7 @@ class DiscussionTab(EnrolledTab):
...
@@ -59,6 +59,7 @@ class DiscussionTab(EnrolledTab):
priority
=
None
priority
=
None
view_name
=
'django_comment_client.forum.views.forum_form_discussion'
view_name
=
'django_comment_client.forum.views.forum_form_discussion'
is_hideable
=
settings
.
FEATURES
.
get
(
'ALLOW_HIDING_DISCUSSION_TAB'
,
False
)
is_hideable
=
settings
.
FEATURES
.
get
(
'ALLOW_HIDING_DISCUSSION_TAB'
,
False
)
is_default
=
False
@classmethod
@classmethod
def
is_enabled
(
cls
,
course
,
user
=
None
):
def
is_enabled
(
cls
,
course
,
user
=
None
):
...
...
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