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
7461a2fd
Commit
7461a2fd
authored
Jun 04, 2015
by
Diana Huang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor and merge CourseViewType and CourseTab.
TNL-2321
parent
8d1651cc
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
28 changed files
with
442 additions
and
514 deletions
+442
-514
cms/djangoapps/contentstore/views/course.py
+3
-3
cms/djangoapps/contentstore/views/helpers.py
+1
-1
cms/djangoapps/contentstore/views/tabs.py
+1
-2
cms/templates/edit-tabs.html
+1
-1
common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py
+7
-1
common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py
+4
-2
common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py
+42
-34
common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py
+27
-21
common/lib/xmodule/xmodule/modulestore/tests/utils.py
+8
-0
common/lib/xmodule/xmodule/tabs.py
+125
-35
lms/djangoapps/ccx/plugins.py
+4
-4
lms/djangoapps/course_wiki/tab.py
+4
-4
lms/djangoapps/courseware/tabs.py
+74
-111
lms/djangoapps/courseware/tests/test_tabs.py
+27
-26
lms/djangoapps/courseware/views.py
+3
-3
lms/djangoapps/django_comment_client/forum/views.py
+4
-4
lms/djangoapps/edxnotes/plugins.py
+5
-5
lms/djangoapps/instructor/views/instructor_dashboard.py
+3
-3
lms/djangoapps/notes/views.py
+4
-4
lms/djangoapps/open_ended_grading/views.py
+7
-8
lms/djangoapps/teams/plugins.py
+4
-4
openedx/core/djangoapps/course_views/__init__.py
+0
-0
openedx/core/djangoapps/course_views/course_views.py
+0
-201
openedx/core/djangoapps/course_views/tests/__init__.py
+0
-0
openedx/core/lib/course_tabs.py
+47
-0
openedx/core/lib/tests/test_course_tab_api.py
+4
-4
openedx/core/lib/tests/test_course_tabs.py
+14
-14
setup.py
+19
-19
No files found.
cms/djangoapps/contentstore/views/course.py
View file @
7461a2fd
...
@@ -23,7 +23,7 @@ from xmodule.error_module import ErrorDescriptor
...
@@ -23,7 +23,7 @@ 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
from
openedx.core.
djangoapps.course_views.course_views
import
CourseViewType
Manager
from
openedx.core.
lib.course_tabs
import
CourseTabPlugin
Manager
from
xmodule.modulestore
import
EdxJSONEncoder
from
xmodule.modulestore
import
EdxJSONEncoder
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
,
DuplicateCourseError
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
,
DuplicateCourseError
from
opaque_keys
import
InvalidKeyError
from
opaque_keys
import
InvalidKeyError
...
@@ -998,7 +998,7 @@ def _refresh_course_tabs(request, course_module):
...
@@ -998,7 +998,7 @@ def _refresh_course_tabs(request, course_module):
Adds or removes a course tab based upon whether it is enabled.
Adds or removes a course tab based upon whether it is enabled.
"""
"""
tab_panel
=
{
tab_panel
=
{
"type"
:
tab_type
.
nam
e
,
"type"
:
tab_type
.
typ
e
,
"name"
:
tab_type
.
title
,
"name"
:
tab_type
.
title
,
}
}
has_tab
=
tab_panel
in
tabs
has_tab
=
tab_panel
in
tabs
...
@@ -1010,7 +1010,7 @@ def _refresh_course_tabs(request, course_module):
...
@@ -1010,7 +1010,7 @@ def _refresh_course_tabs(request, course_module):
course_tabs
=
copy
.
copy
(
course_module
.
tabs
)
course_tabs
=
copy
.
copy
(
course_module
.
tabs
)
# Additionally update any tabs that are provided by non-dynamic course views
# Additionally update any tabs that are provided by non-dynamic course views
for
tab_type
in
Course
ViewTypeManager
.
get_course_view
_types
():
for
tab_type
in
Course
TabPluginManager
.
get_tab
_types
():
if
not
tab_type
.
is_dynamic
and
tab_type
.
is_default
:
if
not
tab_type
.
is_dynamic
and
tab_type
.
is_default
:
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
)
...
...
cms/djangoapps/contentstore/views/helpers.py
View file @
7461a2fd
...
@@ -12,13 +12,13 @@ from django.http import HttpResponse
...
@@ -12,13 +12,13 @@ from django.http import HttpResponse
from
django.shortcuts
import
redirect
from
django.shortcuts
import
redirect
from
django.utils.translation
import
ugettext
as
_
from
django.utils.translation
import
ugettext
as
_
from
openedx.core.djangoapps.course_views.course_views
import
StaticTab
from
edxmako.shortcuts
import
render_to_string
,
render_to_response
from
edxmako.shortcuts
import
render_to_string
,
render_to_response
from
opaque_keys.edx.keys
import
UsageKey
from
opaque_keys.edx.keys
import
UsageKey
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
import
dogstats_wrapper
as
dog_stats_api
import
dogstats_wrapper
as
dog_stats_api
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.x_module
import
DEPRECATION_VSCOMPAT_EVENT
from
xmodule.x_module
import
DEPRECATION_VSCOMPAT_EVENT
from
xmodule.tabs
import
StaticTab
from
contentstore.utils
import
reverse_course_url
,
reverse_library_url
,
reverse_usage_url
from
contentstore.utils
import
reverse_course_url
,
reverse_library_url
,
reverse_usage_url
from
models.settings.course_grading
import
CourseGradingModel
from
models.settings.course_grading
import
CourseGradingModel
...
...
cms/djangoapps/contentstore/views/tabs.py
View file @
7461a2fd
...
@@ -14,9 +14,8 @@ from django.views.decorators.http import require_http_methods
...
@@ -14,9 +14,8 @@ from django.views.decorators.http import require_http_methods
from
edxmako.shortcuts
import
render_to_response
from
edxmako.shortcuts
import
render_to_response
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.tabs
import
CourseTabList
,
CourseTab
,
InvalidTabsException
from
xmodule.tabs
import
CourseTabList
,
CourseTab
,
InvalidTabsException
,
StaticTab
from
opaque_keys.edx.keys
import
CourseKey
,
UsageKey
from
opaque_keys.edx.keys
import
CourseKey
,
UsageKey
from
openedx.core.djangoapps.course_views.course_views
import
StaticTab
from
..utils
import
get_lms_link_for_item
from
..utils
import
get_lms_link_for_item
...
...
cms/templates/edit-tabs.html
View file @
7461a2fd
...
@@ -4,7 +4,7 @@
...
@@ -4,7 +4,7 @@
<
%!
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
from
django
.
utils
.
translation
import
ugettext
as
_
from
django
.
core
.
urlresolvers
import
reverse
from
django
.
core
.
urlresolvers
import
reverse
from
openedx
.
core
.
djangoapps
.
course_views
.
course_view
s
import
StaticTab
from
xmodule
.
tab
s
import
StaticTab
from
django
.
template
.
defaultfilters
import
escapejs
from
django
.
template
.
defaultfilters
import
escapejs
%
>
%
>
<
%
block
name=
"title"
>
${_("Pages")}
</
%
block>
<
%
block
name=
"title"
>
${_("Pages")}
</
%
block>
...
...
common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py
View file @
7461a2fd
...
@@ -21,6 +21,7 @@ from tempfile import mkdtemp
...
@@ -21,6 +21,7 @@ from tempfile import mkdtemp
import
ddt
import
ddt
from
nose.plugins.attrib
import
attr
from
nose.plugins.attrib
import
attr
from
mock
import
patch
from
xmodule.tests
import
CourseComparisonTest
from
xmodule.tests
import
CourseComparisonTest
from
xmodule.modulestore.mongo.base
import
ModuleStoreEnum
from
xmodule.modulestore.mongo.base
import
ModuleStoreEnum
...
@@ -31,6 +32,7 @@ from xmodule.modulestore.xml_importer import import_course_from_xml
...
@@ -31,6 +32,7 @@ from xmodule.modulestore.xml_importer import import_course_from_xml
from
xmodule.modulestore.xml_exporter
import
export_course_to_xml
from
xmodule.modulestore.xml_exporter
import
export_course_to_xml
from
xmodule.modulestore.split_mongo.split_draft
import
DraftVersioningModuleStore
from
xmodule.modulestore.split_mongo.split_draft
import
DraftVersioningModuleStore
from
xmodule.modulestore.tests.mongo_connection
import
MONGO_PORT_NUM
,
MONGO_HOST
from
xmodule.modulestore.tests.mongo_connection
import
MONGO_PORT_NUM
,
MONGO_HOST
from
xmodule.modulestore.tests.utils
import
mock_tab_from_json
from
xmodule.modulestore.inheritance
import
InheritanceMixin
from
xmodule.modulestore.inheritance
import
InheritanceMixin
from
xmodule.partitions.tests.test_partitions
import
PartitionTestCase
from
xmodule.partitions.tests.test_partitions
import
PartitionTestCase
from
xmodule.x_module
import
XModuleMixin
from
xmodule.x_module
import
XModuleMixin
...
@@ -365,6 +367,7 @@ class CrossStoreXMLRoundtrip(CourseComparisonTest, PartitionTestCase):
...
@@ -365,6 +367,7 @@ class CrossStoreXMLRoundtrip(CourseComparisonTest, PartitionTestCase):
self
.
export_dir
=
mkdtemp
()
self
.
export_dir
=
mkdtemp
()
self
.
addCleanup
(
rmtree
,
self
.
export_dir
,
ignore_errors
=
True
)
self
.
addCleanup
(
rmtree
,
self
.
export_dir
,
ignore_errors
=
True
)
@patch
(
'xmodule.tabs.CourseTab.from_json'
,
side_effect
=
mock_tab_from_json
)
@ddt.data
(
*
itertools
.
product
(
@ddt.data
(
*
itertools
.
product
(
MODULESTORE_SETUPS
,
MODULESTORE_SETUPS
,
MODULESTORE_SETUPS
,
MODULESTORE_SETUPS
,
...
@@ -373,7 +376,10 @@ class CrossStoreXMLRoundtrip(CourseComparisonTest, PartitionTestCase):
...
@@ -373,7 +376,10 @@ class CrossStoreXMLRoundtrip(CourseComparisonTest, PartitionTestCase):
COURSE_DATA_NAMES
,
COURSE_DATA_NAMES
,
))
))
@ddt.unpack
@ddt.unpack
def
test_round_trip
(
self
,
source_builder
,
dest_builder
,
source_content_builder
,
dest_content_builder
,
course_data_name
):
def
test_round_trip
(
self
,
source_builder
,
dest_builder
,
source_content_builder
,
dest_content_builder
,
course_data_name
,
_mock_tab_from_json
):
# Construct the contentstore for storing the first import
# Construct the contentstore for storing the first import
with
source_content_builder
.
build
()
as
source_content
:
with
source_content_builder
.
build
()
as
source_content
:
# Construct the modulestore for storing the first import (using the previously created contentstore)
# Construct the modulestore for storing the first import (using the previously created contentstore)
...
...
common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py
View file @
7461a2fd
...
@@ -11,6 +11,7 @@ import mimetypes
...
@@ -11,6 +11,7 @@ import mimetypes
from
unittest
import
skip
from
unittest
import
skip
from
uuid
import
uuid4
from
uuid
import
uuid4
from
contextlib
import
contextmanager
from
contextlib
import
contextmanager
from
mock
import
patch
# Mixed modulestore depends on django, so we'll manually configure some django settings
# Mixed modulestore depends on django, so we'll manually configure some django settings
# before importing the module
# before importing the module
...
@@ -47,7 +48,7 @@ from xmodule.modulestore.mixed import MixedModuleStore
...
@@ -47,7 +48,7 @@ from xmodule.modulestore.mixed import MixedModuleStore
from
xmodule.modulestore.search
import
path_to_location
,
navigation_index
from
xmodule.modulestore.search
import
path_to_location
,
navigation_index
from
xmodule.modulestore.tests.factories
import
check_mongo_calls
,
check_exact_number_of_calls
,
\
from
xmodule.modulestore.tests.factories
import
check_mongo_calls
,
check_exact_number_of_calls
,
\
mongo_uses_error_check
mongo_uses_error_check
from
xmodule.modulestore.tests.utils
import
create_modulestore_instance
,
LocationMixin
from
xmodule.modulestore.tests.utils
import
create_modulestore_instance
,
LocationMixin
,
mock_tab_from_json
from
xmodule.modulestore.tests.mongo_connection
import
MONGO_PORT_NUM
,
MONGO_HOST
from
xmodule.modulestore.tests.mongo_connection
import
MONGO_PORT_NUM
,
MONGO_HOST
from
xmodule.tests
import
DATA_DIR
,
CourseComparisonTest
from
xmodule.tests
import
DATA_DIR
,
CourseComparisonTest
...
@@ -2057,8 +2058,9 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
...
@@ -2057,8 +2058,9 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
self
.
store
.
clone_course
(
course_key
,
dest_course_id
,
self
.
user_id
)
self
.
store
.
clone_course
(
course_key
,
dest_course_id
,
self
.
user_id
)
self
.
assertEqual
(
receiver
.
call_count
,
1
)
self
.
assertEqual
(
receiver
.
call_count
,
1
)
@patch
(
'xmodule.tabs.CourseTab.from_json'
,
side_effect
=
mock_tab_from_json
)
@ddt.data
(
ModuleStoreEnum
.
Type
.
mongo
,
ModuleStoreEnum
.
Type
.
split
)
@ddt.data
(
ModuleStoreEnum
.
Type
.
mongo
,
ModuleStoreEnum
.
Type
.
split
)
def
test_course_publish_signal_import_firing
(
self
,
default
):
def
test_course_publish_signal_import_firing
(
self
,
default
,
_from_json
):
with
MongoContentstoreBuilder
()
.
build
()
as
contentstore
:
with
MongoContentstoreBuilder
()
.
build
()
as
contentstore
:
self
.
store
=
MixedModuleStore
(
self
.
store
=
MixedModuleStore
(
contentstore
=
contentstore
,
contentstore
=
contentstore
,
...
...
common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py
View file @
7461a2fd
...
@@ -17,6 +17,7 @@ from uuid import uuid4
...
@@ -17,6 +17,7 @@ from uuid import uuid4
from
datetime
import
datetime
from
datetime
import
datetime
from
pytz
import
UTC
from
pytz
import
UTC
import
unittest
import
unittest
from
mock
import
patch
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
from
xblock.fields
import
Scope
,
Reference
,
ReferenceList
,
ReferenceValueDict
from
xblock.fields
import
Scope
,
Reference
,
ReferenceList
,
ReferenceValueDict
...
@@ -41,7 +42,7 @@ from git.test.lib.asserts import assert_not_none
...
@@ -41,7 +42,7 @@ from git.test.lib.asserts import assert_not_none
from
xmodule.x_module
import
XModuleMixin
from
xmodule.x_module
import
XModuleMixin
from
xmodule.modulestore.mongo.base
import
as_draft
from
xmodule.modulestore.mongo.base
import
as_draft
from
xmodule.modulestore.tests.mongo_connection
import
MONGO_PORT_NUM
,
MONGO_HOST
from
xmodule.modulestore.tests.mongo_connection
import
MONGO_PORT_NUM
,
MONGO_HOST
from
xmodule.modulestore.tests.utils
import
LocationMixin
from
xmodule.modulestore.tests.utils
import
LocationMixin
,
mock_tab_from_json
from
xmodule.modulestore.edit_info
import
EditInfoMixin
from
xmodule.modulestore.edit_info
import
EditInfoMixin
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
from
xmodule.modulestore.inheritance
import
InheritanceMixin
from
xmodule.modulestore.inheritance
import
InheritanceMixin
...
@@ -129,36 +130,38 @@ class TestMongoModuleStoreBase(unittest.TestCase):
...
@@ -129,36 +130,38 @@ class TestMongoModuleStoreBase(unittest.TestCase):
xblock_mixins
=
(
EditInfoMixin
,
InheritanceMixin
,
LocationMixin
,
XModuleMixin
)
xblock_mixins
=
(
EditInfoMixin
,
InheritanceMixin
,
LocationMixin
,
XModuleMixin
)
)
)
import_course_from_xml
(
draft_store
,
999
,
DATA_DIR
,
cls
.
courses
,
static_content_store
=
content_store
)
# also test a course with no importing of static content
with
patch
(
'xmodule.tabs.CourseTab.from_json'
,
side_effect
=
mock_tab_from_json
):
import_course_from_xml
(
import_course_from_xml
(
draft_store
,
draft_store
,
999
,
999
,
DATA_DIR
,
DATA_DIR
,
[
'test_import_course'
],
cls
.
courses
,
static_content_store
=
content_store
,
static_content_store
=
content_store
do_import_static
=
False
,
)
verbose
=
True
)
# also import a course under a different course_id (especially ORG)
# also test a course with no importing of static content
import_course_from_xml
(
import_course_from_xml
(
draft_store
,
draft_store
,
999
,
999
,
DATA_DIR
,
DATA_DIR
,
[
'test_import_course'
],
[
'test_import_course'
],
static_content_store
=
content_store
,
static_content_store
=
content_store
,
do_import_static
=
False
,
do_import_static
=
False
,
verbose
=
True
,
verbose
=
True
target_id
=
SlashSeparatedCourseKey
(
'guestx'
,
'foo'
,
'bar'
)
)
)
# also import a course under a different course_id (especially ORG)
import_course_from_xml
(
draft_store
,
999
,
DATA_DIR
,
[
'test_import_course'
],
static_content_store
=
content_store
,
do_import_static
=
False
,
verbose
=
True
,
target_id
=
SlashSeparatedCourseKey
(
'guestx'
,
'foo'
,
'bar'
)
)
return
content_store
,
draft_store
return
content_store
,
draft_store
...
@@ -203,7 +206,8 @@ class TestMongoModuleStore(TestMongoModuleStoreBase):
...
@@ -203,7 +206,8 @@ class TestMongoModuleStore(TestMongoModuleStoreBase):
)
)
assert_equals
(
store
.
get_modulestore_type
(
''
),
ModuleStoreEnum
.
Type
.
mongo
)
assert_equals
(
store
.
get_modulestore_type
(
''
),
ModuleStoreEnum
.
Type
.
mongo
)
def
test_get_courses
(
self
):
@patch
(
'xmodule.tabs.CourseTab.from_json'
,
side_effect
=
mock_tab_from_json
)
def
test_get_courses
(
self
,
_from_json
):
'''Make sure the course objects loaded properly'''
'''Make sure the course objects loaded properly'''
courses
=
self
.
draft_store
.
get_courses
()
courses
=
self
.
draft_store
.
get_courses
()
...
@@ -241,7 +245,8 @@ class TestMongoModuleStore(TestMongoModuleStoreBase):
...
@@ -241,7 +245,8 @@ class TestMongoModuleStore(TestMongoModuleStoreBase):
assert_false
(
self
.
draft_store
.
has_course
(
mix_cased
))
assert_false
(
self
.
draft_store
.
has_course
(
mix_cased
))
assert_true
(
self
.
draft_store
.
has_course
(
mix_cased
,
ignore_case
=
True
))
assert_true
(
self
.
draft_store
.
has_course
(
mix_cased
,
ignore_case
=
True
))
def
test_get_org_courses
(
self
):
@patch
(
'xmodule.tabs.CourseTab.from_json'
,
side_effect
=
mock_tab_from_json
)
def
test_get_org_courses
(
self
,
_from_json
):
"""
"""
Make sure that we can query for a filtered list of courses for a given ORG
Make sure that we can query for a filtered list of courses for a given ORG
"""
"""
...
@@ -437,7 +442,8 @@ class TestMongoModuleStore(TestMongoModuleStoreBase):
...
@@ -437,7 +442,8 @@ class TestMongoModuleStore(TestMongoModuleStoreBase):
{
'displayname'
:
'hello'
}
{
'displayname'
:
'hello'
}
)
)
def
test_get_courses_for_wiki
(
self
):
@patch
(
'xmodule.tabs.CourseTab.from_json'
,
side_effect
=
mock_tab_from_json
)
def
test_get_courses_for_wiki
(
self
,
_from_json
):
"""
"""
Test the get_courses_for_wiki method
Test the get_courses_for_wiki method
"""
"""
...
@@ -552,7 +558,8 @@ class TestMongoModuleStore(TestMongoModuleStoreBase):
...
@@ -552,7 +558,8 @@ class TestMongoModuleStore(TestMongoModuleStoreBase):
check_xblock_fields
()
check_xblock_fields
()
check_mongo_fields
()
check_mongo_fields
()
def
test_export_course_image
(
self
):
@patch
(
'xmodule.tabs.CourseTab.from_json'
,
side_effect
=
mock_tab_from_json
)
def
test_export_course_image
(
self
,
_from_json
):
"""
"""
Test to make sure that we have a course image in the contentstore,
Test to make sure that we have a course image in the contentstore,
then export it to ensure it gets copied to both file locations.
then export it to ensure it gets copied to both file locations.
...
@@ -571,7 +578,8 @@ class TestMongoModuleStore(TestMongoModuleStoreBase):
...
@@ -571,7 +578,8 @@ class TestMongoModuleStore(TestMongoModuleStoreBase):
finally
:
finally
:
shutil
.
rmtree
(
root_dir
)
shutil
.
rmtree
(
root_dir
)
def
test_export_course_image_nondefault
(
self
):
@patch
(
'xmodule.tabs.CourseTab.from_json'
,
side_effect
=
mock_tab_from_json
)
def
test_export_course_image_nondefault
(
self
,
_from_json
):
"""
"""
Make sure that if a non-default image path is specified that we
Make sure that if a non-default image path is specified that we
don't export it to the static default location
don't export it to the static default location
...
...
common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py
View file @
7461a2fd
...
@@ -30,6 +30,7 @@ from xmodule.modulestore.split_mongo.split import SplitMongoModuleStore
...
@@ -30,6 +30,7 @@ from xmodule.modulestore.split_mongo.split import SplitMongoModuleStore
from
xmodule.modulestore.tests.test_modulestore
import
check_has_course_method
from
xmodule.modulestore.tests.test_modulestore
import
check_has_course_method
from
xmodule.modulestore.split_mongo
import
BlockKey
from
xmodule.modulestore.split_mongo
import
BlockKey
from
xmodule.modulestore.tests.mongo_connection
import
MONGO_PORT_NUM
,
MONGO_HOST
from
xmodule.modulestore.tests.mongo_connection
import
MONGO_PORT_NUM
,
MONGO_HOST
from
xmodule.modulestore.tests.utils
import
mock_tab_from_json
from
xmodule.modulestore.edit_info
import
EditInfoMixin
from
xmodule.modulestore.edit_info
import
EditInfoMixin
...
@@ -37,14 +38,6 @@ BRANCH_NAME_DRAFT = ModuleStoreEnum.BranchName.draft
...
@@ -37,14 +38,6 @@ BRANCH_NAME_DRAFT = ModuleStoreEnum.BranchName.draft
BRANCH_NAME_PUBLISHED
=
ModuleStoreEnum
.
BranchName
.
published
BRANCH_NAME_PUBLISHED
=
ModuleStoreEnum
.
BranchName
.
published
def
mock_tab_from_json
(
tab_dict
):
"""
Mocks out the CourseTab.from_json to just return the tab_dict itself so that we don't have to deal
with plugin errors.
"""
return
tab_dict
@attr
(
'mongo'
)
@attr
(
'mongo'
)
class
SplitModuleTest
(
unittest
.
TestCase
):
class
SplitModuleTest
(
unittest
.
TestCase
):
'''
'''
...
@@ -567,7 +560,8 @@ class SplitModuleTest(unittest.TestCase):
...
@@ -567,7 +560,8 @@ class SplitModuleTest(unittest.TestCase):
class
TestHasChildrenAtDepth
(
SplitModuleTest
):
class
TestHasChildrenAtDepth
(
SplitModuleTest
):
"""Test the has_children_at_depth method of XModuleMixin. """
"""Test the has_children_at_depth method of XModuleMixin. """
def
test_has_children_at_depth
(
self
):
@patch
(
'xmodule.tabs.CourseTab.from_json'
,
side_effect
=
mock_tab_from_json
)
def
test_has_children_at_depth
(
self
,
_from_json
):
course_locator
=
CourseLocator
(
course_locator
=
CourseLocator
(
org
=
'testx'
,
course
=
'GreekHero'
,
run
=
"run"
,
branch
=
BRANCH_NAME_DRAFT
org
=
'testx'
,
course
=
'GreekHero'
,
run
=
"run"
,
branch
=
BRANCH_NAME_DRAFT
)
)
...
@@ -628,7 +622,8 @@ class SplitModuleCourseTests(SplitModuleTest):
...
@@ -628,7 +622,8 @@ class SplitModuleCourseTests(SplitModuleTest):
self
.
assertEqual
(
course
.
edited_by
,
"testassist@edx.org"
)
self
.
assertEqual
(
course
.
edited_by
,
"testassist@edx.org"
)
self
.
assertDictEqual
(
course
.
grade_cutoffs
,
{
"Pass"
:
0.45
})
self
.
assertDictEqual
(
course
.
grade_cutoffs
,
{
"Pass"
:
0.45
})
def
test_get_org_courses
(
self
):
@patch
(
'xmodule.tabs.CourseTab.from_json'
,
side_effect
=
mock_tab_from_json
)
def
test_get_org_courses
(
self
,
_from_json
):
courses
=
modulestore
()
.
get_courses
(
branch
=
BRANCH_NAME_DRAFT
,
org
=
'guestx'
)
courses
=
modulestore
()
.
get_courses
(
branch
=
BRANCH_NAME_DRAFT
,
org
=
'guestx'
)
# should have gotten 1 draft courses
# should have gotten 1 draft courses
...
@@ -730,7 +725,8 @@ class SplitModuleCourseTests(SplitModuleTest):
...
@@ -730,7 +725,8 @@ class SplitModuleCourseTests(SplitModuleTest):
with
self
.
assertRaises
(
ItemNotFoundError
):
with
self
.
assertRaises
(
ItemNotFoundError
):
modulestore
()
.
get_course
(
CourseLocator
(
org
=
'testx'
,
course
=
'GreekHero'
,
run
=
"run"
,
branch
=
BRANCH_NAME_PUBLISHED
))
modulestore
()
.
get_course
(
CourseLocator
(
org
=
'testx'
,
course
=
'GreekHero'
,
run
=
"run"
,
branch
=
BRANCH_NAME_PUBLISHED
))
def
test_cache
(
self
):
@patch
(
'xmodule.tabs.CourseTab.from_json'
,
side_effect
=
mock_tab_from_json
)
def
test_cache
(
self
,
_from_json
):
"""
"""
Test that the mechanics of caching work.
Test that the mechanics of caching work.
"""
"""
...
@@ -742,7 +738,8 @@ class SplitModuleCourseTests(SplitModuleTest):
...
@@ -742,7 +738,8 @@ class SplitModuleCourseTests(SplitModuleTest):
self
.
assertIn
(
BlockKey
(
'chapter'
,
'chapter1'
),
block_map
)
self
.
assertIn
(
BlockKey
(
'chapter'
,
'chapter1'
),
block_map
)
self
.
assertIn
(
BlockKey
(
'problem'
,
'problem3_2'
),
block_map
)
self
.
assertIn
(
BlockKey
(
'problem'
,
'problem3_2'
),
block_map
)
def
test_course_successors
(
self
):
@patch
(
'xmodule.tabs.CourseTab.from_json'
,
side_effect
=
mock_tab_from_json
)
def
test_course_successors
(
self
,
_from_json
):
"""
"""
get_course_successors(course_locator, version_history_depth=1)
get_course_successors(course_locator, version_history_depth=1)
"""
"""
...
@@ -779,7 +776,8 @@ class SplitModuleItemTests(SplitModuleTest):
...
@@ -779,7 +776,8 @@ class SplitModuleItemTests(SplitModuleTest):
Item read tests including inheritance
Item read tests including inheritance
'''
'''
def
test_has_item
(
self
):
@patch
(
'xmodule.tabs.CourseTab.from_json'
,
side_effect
=
mock_tab_from_json
)
def
test_has_item
(
self
,
_from_json
):
'''
'''
has_item(BlockUsageLocator)
has_item(BlockUsageLocator)
'''
'''
...
@@ -843,7 +841,8 @@ class SplitModuleItemTests(SplitModuleTest):
...
@@ -843,7 +841,8 @@ class SplitModuleItemTests(SplitModuleTest):
)
)
self
.
assertFalse
(
modulestore
()
.
has_item
(
locator
))
self
.
assertFalse
(
modulestore
()
.
has_item
(
locator
))
def
test_get_item
(
self
):
@patch
(
'xmodule.tabs.CourseTab.from_json'
,
side_effect
=
mock_tab_from_json
)
def
test_get_item
(
self
,
_from_json
):
'''
'''
get_item(blocklocator)
get_item(blocklocator)
'''
'''
...
@@ -1001,7 +1000,8 @@ class SplitModuleItemTests(SplitModuleTest):
...
@@ -1001,7 +1000,8 @@ class SplitModuleItemTests(SplitModuleTest):
parent
=
modulestore
()
.
get_parent_location
(
locator
)
parent
=
modulestore
()
.
get_parent_location
(
locator
)
self
.
assertIsNone
(
parent
)
self
.
assertIsNone
(
parent
)
def
test_get_children
(
self
):
@patch
(
'xmodule.tabs.CourseTab.from_json'
,
side_effect
=
mock_tab_from_json
)
def
test_get_children
(
self
,
_from_json
):
"""
"""
Test the existing get_children method on xdescriptors
Test the existing get_children method on xdescriptors
"""
"""
...
@@ -1354,7 +1354,8 @@ class TestItemCrud(SplitModuleTest):
...
@@ -1354,7 +1354,8 @@ class TestItemCrud(SplitModuleTest):
other_updated
=
modulestore
()
.
update_item
(
other_block
,
self
.
user_id
)
other_updated
=
modulestore
()
.
update_item
(
other_block
,
self
.
user_id
)
self
.
assertIn
(
moved_child
.
version_agnostic
(),
version_agnostic
(
other_updated
.
children
))
self
.
assertIn
(
moved_child
.
version_agnostic
(),
version_agnostic
(
other_updated
.
children
))
def
test_update_definition
(
self
):
@patch
(
'xmodule.tabs.CourseTab.from_json'
,
side_effect
=
mock_tab_from_json
)
def
test_update_definition
(
self
,
_from_json
):
"""
"""
test updating an item's definition: ensure it gets versioned as well as the course getting versioned
test updating an item's definition: ensure it gets versioned as well as the course getting versioned
"""
"""
...
@@ -1625,7 +1626,8 @@ class TestCourseCreation(SplitModuleTest):
...
@@ -1625,7 +1626,8 @@ class TestCourseCreation(SplitModuleTest):
fields
[
'grading_policy'
][
'GRADE_CUTOFFS'
]
fields
[
'grading_policy'
][
'GRADE_CUTOFFS'
]
)
)
def
test_update_course_index
(
self
):
@patch
(
'xmodule.tabs.CourseTab.from_json'
,
side_effect
=
mock_tab_from_json
)
def
test_update_course_index
(
self
,
_from_json
):
"""
"""
Test the versions pointers. NOTE: you can change the org, course, or other things, but
Test the versions pointers. NOTE: you can change the org, course, or other things, but
it's not clear how you'd find them again or associate them w/ existing student history since
it's not clear how you'd find them again or associate them w/ existing student history since
...
@@ -1680,7 +1682,8 @@ class TestCourseCreation(SplitModuleTest):
...
@@ -1680,7 +1682,8 @@ class TestCourseCreation(SplitModuleTest):
dupe_course_key
.
org
,
dupe_course_key
.
course
,
dupe_course_key
.
run
,
user
,
BRANCH_NAME_DRAFT
dupe_course_key
.
org
,
dupe_course_key
.
course
,
dupe_course_key
.
run
,
user
,
BRANCH_NAME_DRAFT
)
)
def
test_bulk_ops_get_courses
(
self
):
@patch
(
'xmodule.tabs.CourseTab.from_json'
,
side_effect
=
mock_tab_from_json
)
def
test_bulk_ops_get_courses
(
self
,
_from_json
):
"""
"""
Test get_courses when some are created, updated, and deleted w/in a bulk operation
Test get_courses when some are created, updated, and deleted w/in a bulk operation
"""
"""
...
@@ -1719,7 +1722,8 @@ class TestInheritance(SplitModuleTest):
...
@@ -1719,7 +1722,8 @@ class TestInheritance(SplitModuleTest):
"""
"""
Test the metadata inheritance mechanism.
Test the metadata inheritance mechanism.
"""
"""
def
test_inheritance
(
self
):
@patch
(
'xmodule.tabs.CourseTab.from_json'
,
side_effect
=
mock_tab_from_json
)
def
test_inheritance
(
self
,
_from_json
):
"""
"""
The actual test
The actual test
"""
"""
...
@@ -1799,7 +1803,8 @@ class TestPublish(SplitModuleTest):
...
@@ -1799,7 +1803,8 @@ class TestPublish(SplitModuleTest):
def
tearDown
(
self
):
def
tearDown
(
self
):
SplitModuleTest
.
tearDown
(
self
)
SplitModuleTest
.
tearDown
(
self
)
def
test_publish_safe
(
self
):
@patch
(
'xmodule.tabs.CourseTab.from_json'
,
side_effect
=
mock_tab_from_json
)
def
test_publish_safe
(
self
,
_from_json
):
"""
"""
Test the standard patterns: publish to new branch, revise and publish
Test the standard patterns: publish to new branch, revise and publish
"""
"""
...
@@ -1868,7 +1873,8 @@ class TestPublish(SplitModuleTest):
...
@@ -1868,7 +1873,8 @@ class TestPublish(SplitModuleTest):
with
self
.
assertRaises
(
ItemNotFoundError
):
with
self
.
assertRaises
(
ItemNotFoundError
):
modulestore
()
.
copy
(
self
.
user_id
,
source_course
,
destination_course
,
[
problem1
],
[])
modulestore
()
.
copy
(
self
.
user_id
,
source_course
,
destination_course
,
[
problem1
],
[])
def
test_move_delete
(
self
):
@patch
(
'xmodule.tabs.CourseTab.from_json'
,
side_effect
=
mock_tab_from_json
)
def
test_move_delete
(
self
,
_from_json
):
"""
"""
Test publishing moves and deletes.
Test publishing moves and deletes.
"""
"""
...
...
common/lib/xmodule/xmodule/modulestore/tests/utils.py
View file @
7461a2fd
...
@@ -54,6 +54,14 @@ def create_modulestore_instance(
...
@@ -54,6 +54,14 @@ def create_modulestore_instance(
)
)
def
mock_tab_from_json
(
tab_dict
):
"""
Mocks out the CourseTab.from_json to just return the tab_dict itself so that we don't have to deal
with plugin errors.
"""
return
tab_dict
class
LocationMixin
(
XBlockMixin
):
class
LocationMixin
(
XBlockMixin
):
"""
"""
Adds a `location` property to an :class:`XBlock` so it is more compatible
Adds a `location` property to an :class:`XBlock` so it is more compatible
...
...
common/lib/xmodule/xmodule/tabs.py
View file @
7461a2fd
...
@@ -28,54 +28,59 @@ class CourseTab(object):
...
@@ -28,54 +28,59 @@ class CourseTab(object):
# subclass, shared by all instances of the subclass.
# subclass, shared by all instances of the subclass.
type
=
''
type
=
''
# The title of the tab, which should be internationalized
title
=
None
# Class property that specifies whether the tab can be hidden for a particular course
# Class property that specifies whether the tab can be hidden for a particular course
is_hideable
=
False
is_hideable
=
False
# Class property that specifies whether the tab is hidden for a particular course
# Class property that specifies whether the tab is hidden for a particular course
is_hidden
=
False
is_hidden
=
False
# The relative priority of this view that affects the ordering (lower numbers shown first)
priority
=
None
# Class property that specifies whether the tab can be moved within a course's list of tabs
# Class property that specifies whether the tab can be moved within a course's list of tabs
is_movable
=
True
is_movable
=
True
# Class property that specifies whether the tab is a collection of other tabs
# Class property that specifies whether the tab is a collection of other tabs
is_collection
=
False
is_collection
=
False
def
__init__
(
self
,
name
,
tab_id
,
link_func
):
# True if this tab is dynamically added to the list of tabs
"""
is_dynamic
=
False
Initializes class members with values passed in by subclasses.
Args:
# True if this tab is a default for the course (when enabled)
name: The name of the tab
is_default
=
True
# True if this tab can be included more than once for a course.
allow_multiple
=
False
tab_id: Intended to be a unique id for this tab, although it is currently not enforced
# If there is a single view associated with this tab, this is the name of it
within this module. It is used by the UI to determine which page is active.
view_name
=
None
link_func: A function that computes the link for the tab,
def
__init__
(
self
,
tab_dict
):
given the course and a reverse-url function as input parameters
"""
"""
Initializes class members with values passed in by subclasses.
self
.
name
=
name
Args:
tab_dict (dict) - a dictionary of parameters used to build the tab.
"""
self
.
tab_id
=
tab_id
self
.
name
=
tab_dict
.
get
(
'name'
,
self
.
title
)
self
.
tab_id
=
tab_dict
.
get
(
'tab_id'
,
getattr
(
self
,
'tab_id'
,
self
.
type
))
self
.
link_func
=
tab_dict
.
get
(
'link_func'
,
link_reverse_func
(
self
.
view_name
))
self
.
link_func
=
link_func
self
.
is_hidden
=
tab_dict
.
get
(
'is_hidden'
,
False
)
def
is_enabled
(
self
,
course
,
user
=
None
):
# pylint: disable=unused-argument
@classmethod
"""
def
is_enabled
(
cls
,
course
,
user
=
None
):
# pylint: disable=unused-argument
Determines whether the tab is enabled for the given course and a particular user.
"""Returns true if this course tab is enabled in the course.
This method is to be overridden by subclasses when applicable. The base class
implementation always returns True.
Args:
Args:
course: An xModule CourseDescriptor
course (CourseDescriptor): the course using the feature
user (User): an optional user interacting with the course (defaults to None)
user: An optional user for whom the tab will be displayed. If none,
then the code should assume a staff user or an author.
Returns:
A boolean value to indicate whether this instance of the tab is enabled.
"""
"""
r
eturn
True
r
aise
NotImplementedError
()
def
get
(
self
,
key
,
default
=
None
):
def
get
(
self
,
key
,
default
=
None
):
"""
"""
...
@@ -98,6 +103,8 @@ class CourseTab(object):
...
@@ -98,6 +103,8 @@ class CourseTab(object):
return
self
.
type
return
self
.
type
elif
key
==
'tab_id'
:
elif
key
==
'tab_id'
:
return
self
.
tab_id
return
self
.
tab_id
elif
key
==
'is_hidden'
:
return
self
.
is_hidden
else
:
else
:
raise
KeyError
(
'Key {0} not present in tab {1}'
.
format
(
key
,
self
.
to_json
()))
raise
KeyError
(
'Key {0} not present in tab {1}'
.
format
(
key
,
self
.
to_json
()))
...
@@ -112,6 +119,8 @@ class CourseTab(object):
...
@@ -112,6 +119,8 @@ class CourseTab(object):
self
.
name
=
value
self
.
name
=
value
elif
key
==
'tab_id'
:
elif
key
==
'tab_id'
:
self
.
tab_id
=
value
self
.
tab_id
=
value
elif
key
==
'is_hidden'
:
self
.
is_hidden
=
value
else
:
else
:
raise
KeyError
(
'Key {0} cannot be set in tab {1}'
.
format
(
key
,
self
.
to_json
()))
raise
KeyError
(
'Key {0} cannot be set in tab {1}'
.
format
(
key
,
self
.
to_json
()))
...
@@ -129,8 +138,10 @@ class CourseTab(object):
...
@@ -129,8 +138,10 @@ class CourseTab(object):
# allow tabs without names; if a name is required, its presence was checked in the validator.
# allow tabs without names; if a name is required, its presence was checked in the validator.
name_is_eq
=
(
other
.
get
(
'name'
)
is
None
or
self
.
name
==
other
[
'name'
])
name_is_eq
=
(
other
.
get
(
'name'
)
is
None
or
self
.
name
==
other
[
'name'
])
is_hidden_eq
=
self
.
is_hidden
==
other
.
get
(
'is_hidden'
,
False
)
# only compare the persisted/serialized members: 'type' and 'name'
# only compare the persisted/serialized members: 'type' and 'name'
return
self
.
type
==
other
.
get
(
'type'
)
and
name_is_eq
return
self
.
type
==
other
.
get
(
'type'
)
and
name_is_eq
and
is_hidden_eq
def
__ne__
(
self
,
other
):
def
__ne__
(
self
,
other
):
"""
"""
...
@@ -170,7 +181,10 @@ class CourseTab(object):
...
@@ -170,7 +181,10 @@ class CourseTab(object):
Returns:
Returns:
a dictionary with keys for the properties of the CourseTab object.
a dictionary with keys for the properties of the CourseTab object.
"""
"""
return
{
'type'
:
self
.
type
,
'name'
:
self
.
name
}
to_json_val
=
{
'type'
:
self
.
type
,
'name'
:
self
.
name
}
if
self
.
is_hidden
:
to_json_val
.
update
({
'is_hidden'
:
True
})
return
to_json_val
@staticmethod
@staticmethod
def
from_json
(
tab_dict
):
def
from_json
(
tab_dict
):
...
@@ -191,22 +205,88 @@ class CourseTab(object):
...
@@ -191,22 +205,88 @@ class CourseTab(object):
InvalidTabsException if the given tab doesn't have the right keys.
InvalidTabsException if the given tab doesn't have the right keys.
"""
"""
# TODO: don't import openedx capabilities from common
# TODO: don't import openedx capabilities from common
from
openedx.core.
djangoapps.course_views.course_views
import
CourseViewType
Manager
from
openedx.core.
lib.course_tabs
import
CourseTabPlugin
Manager
tab_type_name
=
tab_dict
.
get
(
'type'
)
tab_type_name
=
tab_dict
.
get
(
'type'
)
if
tab_type_name
is
None
:
if
tab_type_name
is
None
:
log
.
error
(
'No type included in tab_dict:
%
r'
,
tab_dict
)
log
.
error
(
'No type included in tab_dict:
%
r'
,
tab_dict
)
return
None
return
None
try
:
try
:
tab_type
=
Course
ViewType
Manager
.
get_plugin
(
tab_type_name
)
tab_type
=
Course
TabPlugin
Manager
.
get_plugin
(
tab_type_name
)
except
PluginError
:
except
PluginError
:
log
.
exception
(
log
.
exception
(
"Unknown tab type
%
r Known types:
%
r."
,
"Unknown tab type
%
r Known types:
%
r."
,
tab_type_name
,
tab_type_name
,
Course
ViewTypeManager
.
get_course_view
_types
()
Course
TabPluginManager
.
get_tab
_types
()
)
)
return
None
return
None
tab_type
.
validate
(
tab_dict
)
tab_type
.
validate
(
tab_dict
)
return
tab_type
.
create_tab
(
tab_dict
=
tab_dict
)
return
tab_type
(
tab_dict
=
tab_dict
)
class
StaticTab
(
CourseTab
):
"""
A custom tab.
"""
type
=
'static_tab'
is_default
=
False
# A static tab is never added to a course by default
allow_multiple
=
True
def
__init__
(
self
,
tab_dict
=
None
,
name
=
None
,
url_slug
=
None
):
def
link_func
(
course
,
reverse_func
):
""" Returns a url for a given course and reverse function. """
return
reverse_func
(
self
.
type
,
args
=
[
course
.
id
.
to_deprecated_string
(),
self
.
url_slug
])
self
.
url_slug
=
tab_dict
.
get
(
'url_slug'
)
if
tab_dict
else
url_slug
if
tab_dict
is
None
:
tab_dict
=
dict
()
if
name
is
not
None
:
tab_dict
[
'name'
]
=
name
tab_dict
[
'link_func'
]
=
link_func
tab_dict
[
'tab_id'
]
=
'static_tab_{0}'
.
format
(
self
.
url_slug
)
super
(
StaticTab
,
self
)
.
__init__
(
tab_dict
)
@classmethod
def
is_enabled
(
cls
,
course
,
user
=
None
):
# pylint: disable=unused-argument
"""
Static tabs are viewable to everyone, even anonymous users.
"""
return
True
@classmethod
def
validate
(
cls
,
tab_dict
,
raise_error
=
True
):
"""
Ensures that the specified tab_dict is valid.
"""
return
(
super
(
StaticTab
,
cls
)
.
validate
(
tab_dict
,
raise_error
)
and
key_checker
([
'name'
,
'url_slug'
])(
tab_dict
,
raise_error
))
def
__getitem__
(
self
,
key
):
if
key
==
'url_slug'
:
return
self
.
url_slug
else
:
return
super
(
StaticTab
,
self
)
.
__getitem__
(
key
)
def
__setitem__
(
self
,
key
,
value
):
if
key
==
'url_slug'
:
self
.
url_slug
=
value
else
:
super
(
StaticTab
,
self
)
.
__setitem__
(
key
,
value
)
def
to_json
(
self
):
""" Return a dictionary representation of this tab. """
to_json_val
=
super
(
StaticTab
,
self
)
.
to_json
()
to_json_val
.
update
({
'url_slug'
:
self
.
url_slug
})
return
to_json_val
def
__eq__
(
self
,
other
):
if
not
super
(
StaticTab
,
self
)
.
__eq__
(
other
):
return
False
return
self
.
url_slug
==
other
.
get
(
'url_slug'
)
class
CourseTabList
(
List
):
class
CourseTabList
(
List
):
...
@@ -338,10 +418,10 @@ class CourseTabList(List):
...
@@ -338,10 +418,10 @@ class CourseTabList(List):
# the following tabs should appear only once
# the following tabs should appear only once
# TODO: don't import openedx capabilities from common
# TODO: don't import openedx capabilities from common
from
openedx.core.
djangoapps.course_views.course_views
import
CourseViewType
Manager
from
openedx.core.
lib.course_tabs
import
CourseTabPlugin
Manager
for
course_view_type
in
CourseViewTypeManager
.
get_course_view
_types
():
for
tab_type
in
CourseTabPluginManager
.
get_tab
_types
():
if
not
course_view
_type
.
allow_multiple
:
if
not
tab
_type
.
allow_multiple
:
cls
.
_validate_num_tabs_of_type
(
tabs
,
course_view_type
.
nam
e
,
1
)
cls
.
_validate_num_tabs_of_type
(
tabs
,
tab_type
.
typ
e
,
1
)
@staticmethod
@staticmethod
def
_validate_num_tabs_of_type
(
tabs
,
tab_type
,
max_num
):
def
_validate_num_tabs_of_type
(
tabs
,
tab_type
,
max_num
):
...
@@ -411,6 +491,16 @@ def key_checker(expected_keys):
...
@@ -411,6 +491,16 @@ def key_checker(expected_keys):
return
check
return
check
def
link_reverse_func
(
reverse_name
):
"""
Returns a function that takes in a course and reverse_url_func,
and calls the reverse_url_func with the given reverse_name and course's ID.
This is used to generate the url for a CourseTab without having access to Django's reverse function.
"""
return
lambda
course
,
reverse_url_func
:
reverse_url_func
(
reverse_name
,
args
=
[
course
.
id
.
to_deprecated_string
()])
def
need_name
(
dictionary
,
raise_error
=
True
):
def
need_name
(
dictionary
,
raise_error
=
True
):
"""
"""
Returns whether the 'name' key exists in the given dictionary.
Returns whether the 'name' key exists in the given dictionary.
...
...
lms/djangoapps/ccx/plugins.py
View file @
7461a2fd
...
@@ -5,16 +5,16 @@ Registers the CCX feature for the edX platform.
...
@@ -5,16 +5,16 @@ Registers the CCX feature for the edX platform.
from
django.conf
import
settings
from
django.conf
import
settings
from
django.utils.translation
import
ugettext
as
_
from
django.utils.translation
import
ugettext
as
_
from
openedx.core.djangoapps.course_views.course_views
import
CourseViewType
from
xmodule.tabs
import
CourseTab
from
student.roles
import
CourseCcxCoachRole
from
student.roles
import
CourseCcxCoachRole
class
CcxCourse
ViewType
(
CourseViewType
):
class
CcxCourse
Tab
(
CourseTab
):
"""
"""
The representation of the CCX course
view type.
The representation of the CCX course
tab
"""
"""
nam
e
=
"ccx_coach"
typ
e
=
"ccx_coach"
title
=
_
(
"CCX Coach"
)
title
=
_
(
"CCX Coach"
)
view_name
=
"ccx_coach_dashboard"
view_name
=
"ccx_coach_dashboard"
is_dynamic
=
True
# The CCX view is dynamically added to the set of tabs when it is enabled
is_dynamic
=
True
# The CCX view is dynamically added to the set of tabs when it is enabled
...
...
lms/djangoapps/course_wiki/tab.py
View file @
7461a2fd
...
@@ -6,15 +6,15 @@ a user has on an article.
...
@@ -6,15 +6,15 @@ a user has on an article.
from
django.conf
import
settings
from
django.conf
import
settings
from
django.utils.translation
import
ugettext
as
_
from
django.utils.translation
import
ugettext
as
_
from
courseware.tabs
import
Enrolled
CourseViewType
from
courseware.tabs
import
Enrolled
Tab
class
Wiki
CourseViewType
(
EnrolledCourseViewType
):
class
Wiki
Tab
(
EnrolledTab
):
"""
"""
Defines the Wiki view type that is shown as a course tab.
Defines the Wiki view type that is shown as a course tab.
"""
"""
nam
e
=
"wiki"
typ
e
=
"wiki"
title
=
_
(
'Wiki'
)
title
=
_
(
'Wiki'
)
view_name
=
"course_wiki"
view_name
=
"course_wiki"
is_hideable
=
True
is_hideable
=
True
...
@@ -28,4 +28,4 @@ class WikiCourseViewType(EnrolledCourseViewType):
...
@@ -28,4 +28,4 @@ class WikiCourseViewType(EnrolledCourseViewType):
return
False
return
False
if
course
.
allow_public_wiki_access
:
if
course
.
allow_public_wiki_access
:
return
True
return
True
return
super
(
Wiki
CourseViewType
,
cls
)
.
is_enabled
(
course
,
user
=
user
)
return
super
(
Wiki
Tab
,
cls
)
.
is_enabled
(
course
,
user
=
user
)
lms/djangoapps/courseware/tabs.py
View file @
7461a2fd
This diff is collapsed.
Click to expand it.
lms/djangoapps/courseware/tests/test_tabs.py
View file @
7461a2fd
...
@@ -11,8 +11,8 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
...
@@ -11,8 +11,8 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
from
courseware.courses
import
get_course_by_id
from
courseware.courses
import
get_course_by_id
from
courseware.tabs
import
(
from
courseware.tabs
import
(
get_course_tab_list
,
Courseware
ViewType
,
CourseInfoViewType
,
ProgressCourseViewType
,
get_course_tab_list
,
Courseware
Tab
,
CourseInfoTab
,
ProgressTab
,
StaticCourseViewType
,
ExternalDiscussionCourseViewType
,
ExternalLinkCourseViewType
ExternalDiscussionCourseTab
,
ExternalLinkCourseTab
)
)
from
courseware.tests.helpers
import
get_request_for_user
,
LoginEnrollmentTestCase
from
courseware.tests.helpers
import
get_request_for_user
,
LoginEnrollmentTestCase
from
courseware.tests.factories
import
InstructorFactory
,
StaffFactory
from
courseware.tests.factories
import
InstructorFactory
,
StaffFactory
...
@@ -85,7 +85,7 @@ class TabTestCase(ModuleStoreTestCase):
...
@@ -85,7 +85,7 @@ class TabTestCase(ModuleStoreTestCase):
Can be 'None' if the given tab class does not have any keys to validate.
Can be 'None' if the given tab class does not have any keys to validate.
"""
"""
# create tab
# create tab
tab
=
tab_class
.
create_tab
(
tab_dict
=
dict_tab
)
tab
=
tab_class
(
tab_dict
=
dict_tab
)
# name is as expected
# name is as expected
self
.
assertEqual
(
tab
.
name
,
expected_name
)
self
.
assertEqual
(
tab
.
name
,
expected_name
)
...
@@ -475,17 +475,17 @@ class TabListTestCase(TabTestCase):
...
@@ -475,17 +475,17 @@ class TabListTestCase(TabTestCase):
# invalid tabs
# invalid tabs
self
.
invalid_tabs
=
[
self
.
invalid_tabs
=
[
# less than 2 tabs
# less than 2 tabs
[{
'type'
:
Courseware
ViewType
.
nam
e
}],
[{
'type'
:
Courseware
Tab
.
typ
e
}],
# missing course_info
# missing course_info
[{
'type'
:
Courseware
ViewType
.
nam
e
},
{
'type'
:
'discussion'
,
'name'
:
'fake_name'
}],
[{
'type'
:
Courseware
Tab
.
typ
e
},
{
'type'
:
'discussion'
,
'name'
:
'fake_name'
}],
# incorrect order
# incorrect order
[{
'type'
:
CourseInfo
ViewType
.
name
,
'name'
:
'fake_name'
},
{
'type'
:
CoursewareViewType
.
nam
e
}],
[{
'type'
:
CourseInfo
Tab
.
type
,
'name'
:
'fake_name'
},
{
'type'
:
CoursewareTab
.
typ
e
}],
]
]
# tab types that should appear only once
# tab types that should appear only once
unique_tab_types
=
[
unique_tab_types
=
[
Courseware
ViewType
.
nam
e
,
Courseware
Tab
.
typ
e
,
CourseInfo
ViewType
.
nam
e
,
CourseInfo
Tab
.
typ
e
,
'textbooks'
,
'textbooks'
,
'pdf_textbooks'
,
'pdf_textbooks'
,
'html_textbooks'
,
'html_textbooks'
,
...
@@ -493,8 +493,8 @@ class TabListTestCase(TabTestCase):
...
@@ -493,8 +493,8 @@ class TabListTestCase(TabTestCase):
for
unique_tab_type
in
unique_tab_types
:
for
unique_tab_type
in
unique_tab_types
:
self
.
invalid_tabs
.
append
([
self
.
invalid_tabs
.
append
([
{
'type'
:
Courseware
ViewType
.
nam
e
},
{
'type'
:
Courseware
Tab
.
typ
e
},
{
'type'
:
CourseInfo
ViewType
.
nam
e
,
'name'
:
'fake_name'
},
{
'type'
:
CourseInfo
Tab
.
typ
e
,
'name'
:
'fake_name'
},
# add the unique tab multiple times
# add the unique tab multiple times
{
'type'
:
unique_tab_type
},
{
'type'
:
unique_tab_type
},
{
'type'
:
unique_tab_type
},
{
'type'
:
unique_tab_type
},
...
@@ -502,26 +502,27 @@ class TabListTestCase(TabTestCase):
...
@@ -502,26 +502,27 @@ class TabListTestCase(TabTestCase):
# valid tabs
# valid tabs
self
.
valid_tabs
=
[
self
.
valid_tabs
=
[
# empty list
# any empty list is valid because a default list of tabs will be
# generated to replace the empty list.
[],
[],
# all valid tabs
# all valid tabs
[
[
{
'type'
:
Courseware
ViewType
.
nam
e
},
{
'type'
:
Courseware
Tab
.
typ
e
},
{
'type'
:
CourseInfo
ViewType
.
nam
e
,
'name'
:
'fake_name'
},
{
'type'
:
CourseInfo
Tab
.
typ
e
,
'name'
:
'fake_name'
},
{
'type'
:
'discussion'
,
'name'
:
'fake_name'
},
{
'type'
:
'discussion'
,
'name'
:
'fake_name'
},
{
'type'
:
ExternalLinkCourse
ViewType
.
nam
e
,
'name'
:
'fake_name'
,
'link'
:
'fake_link'
},
{
'type'
:
ExternalLinkCourse
Tab
.
typ
e
,
'name'
:
'fake_name'
,
'link'
:
'fake_link'
},
{
'type'
:
'textbooks'
},
{
'type'
:
'textbooks'
},
{
'type'
:
'pdf_textbooks'
},
{
'type'
:
'pdf_textbooks'
},
{
'type'
:
'html_textbooks'
},
{
'type'
:
'html_textbooks'
},
{
'type'
:
Progress
CourseViewType
.
nam
e
,
'name'
:
'fake_name'
},
{
'type'
:
Progress
Tab
.
typ
e
,
'name'
:
'fake_name'
},
{
'type'
:
StaticCourseViewType
.
nam
e
,
'name'
:
'fake_name'
,
'url_slug'
:
'schlug'
},
{
'type'
:
xmodule_tabs
.
StaticTab
.
typ
e
,
'name'
:
'fake_name'
,
'url_slug'
:
'schlug'
},
{
'type'
:
'syllabus'
},
{
'type'
:
'syllabus'
},
],
],
# with external discussion
# with external discussion
[
[
{
'type'
:
Courseware
ViewType
.
nam
e
},
{
'type'
:
Courseware
Tab
.
typ
e
},
{
'type'
:
CourseInfo
ViewType
.
nam
e
,
'name'
:
'fake_name'
},
{
'type'
:
CourseInfo
Tab
.
typ
e
,
'name'
:
'fake_name'
},
{
'type'
:
ExternalDiscussionCourse
ViewType
.
nam
e
,
'name'
:
'fake_name'
,
'link'
:
'fake_link'
}
{
'type'
:
ExternalDiscussionCourse
Tab
.
typ
e
,
'name'
:
'fake_name'
,
'link'
:
'fake_link'
}
],
],
]
]
...
@@ -550,8 +551,8 @@ class ValidateTabsTestCase(TabListTestCase):
...
@@ -550,8 +551,8 @@ class ValidateTabsTestCase(TabListTestCase):
tab_list
=
xmodule_tabs
.
CourseTabList
()
tab_list
=
xmodule_tabs
.
CourseTabList
()
self
.
assertEquals
(
self
.
assertEquals
(
len
(
tab_list
.
from_json
([
len
(
tab_list
.
from_json
([
{
'type'
:
Courseware
ViewType
.
nam
e
},
{
'type'
:
Courseware
Tab
.
typ
e
},
{
'type'
:
CourseInfo
ViewType
.
nam
e
,
'name'
:
'fake_name'
},
{
'type'
:
CourseInfo
Tab
.
typ
e
,
'name'
:
'fake_name'
},
{
'type'
:
'no_such_type'
}
{
'type'
:
'no_such_type'
}
])),
])),
2
2
...
@@ -660,10 +661,10 @@ class ProgressTestCase(TabTestCase):
...
@@ -660,10 +661,10 @@ class ProgressTestCase(TabTestCase):
def
check_progress_tab
(
self
):
def
check_progress_tab
(
self
):
"""Helper function for verifying the progress tab."""
"""Helper function for verifying the progress tab."""
return
self
.
check_tab
(
return
self
.
check_tab
(
tab_class
=
Progress
CourseViewType
,
tab_class
=
Progress
Tab
,
dict_tab
=
{
'type'
:
Progress
CourseViewType
.
nam
e
,
'name'
:
'same'
},
dict_tab
=
{
'type'
:
Progress
Tab
.
typ
e
,
'name'
:
'same'
},
expected_link
=
self
.
reverse
(
'progress'
,
args
=
[
self
.
course
.
id
.
to_deprecated_string
()]),
expected_link
=
self
.
reverse
(
'progress'
,
args
=
[
self
.
course
.
id
.
to_deprecated_string
()]),
expected_tab_id
=
Progress
CourseViewType
.
nam
e
,
expected_tab_id
=
Progress
Tab
.
typ
e
,
invalid_dict_tab
=
None
,
invalid_dict_tab
=
None
,
)
)
...
@@ -692,8 +693,8 @@ class StaticTabTestCase(TabTestCase):
...
@@ -692,8 +693,8 @@ class StaticTabTestCase(TabTestCase):
url_slug
=
'schmug'
url_slug
=
'schmug'
tab
=
self
.
check_tab
(
tab
=
self
.
check_tab
(
tab_class
=
StaticCourseViewType
,
tab_class
=
xmodule_tabs
.
StaticTab
,
dict_tab
=
{
'type'
:
StaticCourseViewType
.
nam
e
,
'name'
:
'same'
,
'url_slug'
:
url_slug
},
dict_tab
=
{
'type'
:
xmodule_tabs
.
StaticTab
.
typ
e
,
'name'
:
'same'
,
'url_slug'
:
url_slug
},
expected_link
=
self
.
reverse
(
'static_tab'
,
args
=
[
self
.
course
.
id
.
to_deprecated_string
(),
url_slug
]),
expected_link
=
self
.
reverse
(
'static_tab'
,
args
=
[
self
.
course
.
id
.
to_deprecated_string
(),
url_slug
]),
expected_tab_id
=
'static_tab_schmug'
,
expected_tab_id
=
'static_tab_schmug'
,
invalid_dict_tab
=
self
.
fake_dict_tab
,
invalid_dict_tab
=
self
.
fake_dict_tab
,
...
...
lms/djangoapps/courseware/views.py
View file @
7461a2fd
...
@@ -1135,9 +1135,9 @@ def notification_image_for_tab(course_tab, user, course):
...
@@ -1135,9 +1135,9 @@ def notification_image_for_tab(course_tab, user, course):
"""
"""
tab_notification_handlers
=
{
tab_notification_handlers
=
{
StaffGradingTab
.
nam
e
:
open_ended_notifications
.
staff_grading_notifications
,
StaffGradingTab
.
typ
e
:
open_ended_notifications
.
staff_grading_notifications
,
PeerGradingTab
.
nam
e
:
open_ended_notifications
.
peer_grading_notifications
,
PeerGradingTab
.
typ
e
:
open_ended_notifications
.
peer_grading_notifications
,
OpenEndedGradingTab
.
nam
e
:
open_ended_notifications
.
combined_notifications
OpenEndedGradingTab
.
typ
e
:
open_ended_notifications
.
combined_notifications
}
}
if
course_tab
.
name
in
tab_notification_handlers
:
if
course_tab
.
name
in
tab_notification_handlers
:
...
...
lms/djangoapps/django_comment_client/forum/views.py
View file @
7461a2fd
...
@@ -25,7 +25,7 @@ from openedx.core.djangoapps.course_groups.cohorts import (
...
@@ -25,7 +25,7 @@ from openedx.core.djangoapps.course_groups.cohorts import (
get_course_cohorts
,
get_course_cohorts
,
is_commentable_cohorted
is_commentable_cohorted
)
)
from
courseware.tabs
import
Enrolled
CourseViewType
from
courseware.tabs
import
Enrolled
Tab
from
courseware.access
import
has_access
from
courseware.access
import
has_access
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
ccx.overrides
import
get_current_ccx
from
ccx.overrides
import
get_current_ccx
...
@@ -49,19 +49,19 @@ PAGES_NEARBY_DELTA = 2
...
@@ -49,19 +49,19 @@ PAGES_NEARBY_DELTA = 2
log
=
logging
.
getLogger
(
"edx.discussions"
)
log
=
logging
.
getLogger
(
"edx.discussions"
)
class
Discussion
CourseViewType
(
EnrolledCourseViewType
):
class
Discussion
Tab
(
EnrolledTab
):
"""
"""
A tab for the cs_comments_service forums.
A tab for the cs_comments_service forums.
"""
"""
nam
e
=
'discussion'
typ
e
=
'discussion'
title
=
_
(
'Discussion'
)
title
=
_
(
'Discussion'
)
priority
=
None
priority
=
None
view_name
=
'django_comment_client.forum.views.forum_form_discussion'
view_name
=
'django_comment_client.forum.views.forum_form_discussion'
@classmethod
@classmethod
def
is_enabled
(
cls
,
course
,
user
=
None
):
def
is_enabled
(
cls
,
course
,
user
=
None
):
if
not
super
(
Discussion
CourseViewType
,
cls
)
.
is_enabled
(
course
,
user
):
if
not
super
(
Discussion
Tab
,
cls
)
.
is_enabled
(
course
,
user
):
return
False
return
False
if
settings
.
FEATURES
.
get
(
'CUSTOM_COURSES_EDX'
,
False
):
if
settings
.
FEATURES
.
get
(
'CUSTOM_COURSES_EDX'
,
False
):
...
...
lms/djangoapps/edxnotes/plugins.py
View file @
7461a2fd
...
@@ -4,15 +4,15 @@ Registers the "edX Notes" feature for the edX platform.
...
@@ -4,15 +4,15 @@ Registers the "edX Notes" feature for the edX platform.
from
django.utils.translation
import
ugettext
as
_
from
django.utils.translation
import
ugettext
as
_
from
courseware.tabs
import
Enrolled
CourseViewType
from
courseware.tabs
import
Enrolled
Tab
class
EdxNotes
CourseViewType
(
EnrolledCourseViewType
):
class
EdxNotes
Tab
(
EnrolledTab
):
"""
"""
The representation of the edX Notes course
view
type.
The representation of the edX Notes course
tab
type.
"""
"""
nam
e
=
"edxnotes"
typ
e
=
"edxnotes"
title
=
_
(
"Notes"
)
title
=
_
(
"Notes"
)
view_name
=
"edxnotes"
view_name
=
"edxnotes"
...
@@ -25,6 +25,6 @@ class EdxNotesCourseViewType(EnrolledCourseViewType):
...
@@ -25,6 +25,6 @@ class EdxNotesCourseViewType(EnrolledCourseViewType):
settings (dict): a dict of configuration settings
settings (dict): a dict of configuration settings
user (User): the user interacting with the course
user (User): the user interacting with the course
"""
"""
if
not
super
(
EdxNotes
CourseViewType
,
cls
)
.
is_enabled
(
course
,
user
=
user
):
if
not
super
(
EdxNotes
Tab
,
cls
)
.
is_enabled
(
course
,
user
=
user
):
return
False
return
False
return
course
.
edxnotes
return
course
.
edxnotes
lms/djangoapps/instructor/views/instructor_dashboard.py
View file @
7461a2fd
...
@@ -26,6 +26,7 @@ from lms.djangoapps.lms_xblock.runtime import quote_slashes
...
@@ -26,6 +26,7 @@ from lms.djangoapps.lms_xblock.runtime import quote_slashes
from
openedx.core.lib.xblock_utils
import
wrap_xblock
from
openedx.core.lib.xblock_utils
import
wrap_xblock
from
xmodule.html_module
import
HtmlDescriptor
from
xmodule.html_module
import
HtmlDescriptor
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.tabs
import
CourseTab
from
xblock.field_data
import
DictFieldData
from
xblock.field_data
import
DictFieldData
from
xblock.fields
import
ScopeIds
from
xblock.fields
import
ScopeIds
from
courseware.access
import
has_access
from
courseware.access
import
has_access
...
@@ -38,7 +39,6 @@ from course_modes.models import CourseMode, CourseModesArchive
...
@@ -38,7 +39,6 @@ from course_modes.models import CourseMode, CourseModesArchive
from
student.roles
import
CourseFinanceAdminRole
,
CourseSalesAdminRole
from
student.roles
import
CourseFinanceAdminRole
,
CourseSalesAdminRole
from
certificates.models
import
CertificateGenerationConfiguration
from
certificates.models
import
CertificateGenerationConfiguration
from
certificates
import
api
as
certs_api
from
certificates
import
api
as
certs_api
from
openedx.core.djangoapps.course_views.course_views
import
CourseViewType
from
class_dashboard.dashboard_data
import
get_section_display_name
,
get_array_section_has_problem
from
class_dashboard.dashboard_data
import
get_section_display_name
,
get_array_section_has_problem
from
.tools
import
get_units_with_due_date
,
title_or_url
,
bulk_email_is_enabled_for_course
from
.tools
import
get_units_with_due_date
,
title_or_url
,
bulk_email_is_enabled_for_course
...
@@ -47,12 +47,12 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
...
@@ -47,12 +47,12 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
class
InstructorDashboard
ViewType
(
CourseViewType
):
class
InstructorDashboard
Tab
(
CourseTab
):
"""
"""
Defines the Instructor Dashboard view type that is shown as a course tab.
Defines the Instructor Dashboard view type that is shown as a course tab.
"""
"""
nam
e
=
"instructor"
typ
e
=
"instructor"
title
=
_
(
'Instructor'
)
title
=
_
(
'Instructor'
)
view_name
=
"instructor_dashboard"
view_name
=
"instructor_dashboard"
is_dynamic
=
True
# The "Instructor" tab is instead dynamically added when it is enabled
is_dynamic
=
True
# The "Instructor" tab is instead dynamically added when it is enabled
...
...
lms/djangoapps/notes/views.py
View file @
7461a2fd
...
@@ -9,7 +9,7 @@ from django.http import Http404
...
@@ -9,7 +9,7 @@ from django.http import Http404
from
edxmako.shortcuts
import
render_to_response
from
edxmako.shortcuts
import
render_to_response
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
courseware.courses
import
get_course_with_access
from
courseware.courses
import
get_course_with_access
from
courseware.tabs
import
Enrolled
CourseViewType
from
courseware.tabs
import
Enrolled
Tab
from
notes.models
import
Note
from
notes.models
import
Note
from
notes.utils
import
notes_enabled_for_course
from
notes.utils
import
notes_enabled_for_course
from
xmodule.annotator_token
import
retrieve_token
from
xmodule.annotator_token
import
retrieve_token
...
@@ -40,16 +40,16 @@ def notes(request, course_id):
...
@@ -40,16 +40,16 @@ def notes(request, course_id):
return
render_to_response
(
'notes.html'
,
context
)
return
render_to_response
(
'notes.html'
,
context
)
class
Notes
CourseViewType
(
EnrolledCourseViewType
):
class
Notes
Tab
(
EnrolledTab
):
"""
"""
A tab for the course notes.
A tab for the course notes.
"""
"""
nam
e
=
'notes'
typ
e
=
'notes'
title
=
_
(
"My Notes"
)
title
=
_
(
"My Notes"
)
view_name
=
"notes"
view_name
=
"notes"
@classmethod
@classmethod
def
is_enabled
(
cls
,
course
,
user
=
None
):
def
is_enabled
(
cls
,
course
,
user
=
None
):
if
not
super
(
Notes
CourseViewType
,
cls
)
.
is_enabled
(
course
,
user
):
if
not
super
(
Notes
Tab
,
cls
)
.
is_enabled
(
course
,
user
):
return
False
return
False
return
settings
.
FEATURES
.
get
(
'ENABLE_STUDENT_NOTES'
)
and
"notes"
in
course
.
advanced_modules
return
settings
.
FEATURES
.
get
(
'ENABLE_STUDENT_NOTES'
)
and
"notes"
in
course
.
advanced_modules
lms/djangoapps/open_ended_grading/views.py
View file @
7461a2fd
...
@@ -4,11 +4,10 @@ from django.views.decorators.cache import cache_control
...
@@ -4,11 +4,10 @@ from django.views.decorators.cache import cache_control
from
edxmako.shortcuts
import
render_to_response
from
edxmako.shortcuts
import
render_to_response
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
from
openedx.core.djangoapps.course_views.course_views
import
CourseViewType
from
courseware.courses
import
get_course_with_access
from
courseware.courses
import
get_course_with_access
from
courseware.access
import
has_access
from
courseware.access
import
has_access
from
courseware.tabs
import
Enrolled
CourseViewType
from
courseware.tabs
import
Enrolled
Tab
from
xmodule.open_ended_grading_classes.grading_service_module
import
GradingServiceError
from
xmodule.open_ended_grading_classes.grading_service_module
import
GradingServiceError
import
json
import
json
...
@@ -66,11 +65,11 @@ ALERT_DICT = {
...
@@ -66,11 +65,11 @@ ALERT_DICT = {
}
}
class
StaffGradingTab
(
CourseViewType
):
class
StaffGradingTab
(
EnrolledTab
):
"""
"""
A tab for staff grading.
A tab for staff grading.
"""
"""
nam
e
=
'staff_grading'
typ
e
=
'staff_grading'
title
=
_
(
"Staff grading"
)
title
=
_
(
"Staff grading"
)
view_name
=
"staff_grading"
view_name
=
"staff_grading"
...
@@ -81,11 +80,11 @@ class StaffGradingTab(CourseViewType):
...
@@ -81,11 +80,11 @@ class StaffGradingTab(CourseViewType):
return
"combinedopenended"
in
course
.
advanced_modules
return
"combinedopenended"
in
course
.
advanced_modules
class
PeerGradingTab
(
Enrolled
CourseViewType
):
class
PeerGradingTab
(
Enrolled
Tab
):
"""
"""
A tab for peer grading.
A tab for peer grading.
"""
"""
nam
e
=
'peer_grading'
typ
e
=
'peer_grading'
# Translators: "Peer grading" appears on a tab that allows
# Translators: "Peer grading" appears on a tab that allows
# students to view open-ended problems that require grading
# students to view open-ended problems that require grading
title
=
_
(
"Peer grading"
)
title
=
_
(
"Peer grading"
)
...
@@ -98,11 +97,11 @@ class PeerGradingTab(EnrolledCourseViewType):
...
@@ -98,11 +97,11 @@ class PeerGradingTab(EnrolledCourseViewType):
return
"combinedopenended"
in
course
.
advanced_modules
return
"combinedopenended"
in
course
.
advanced_modules
class
OpenEndedGradingTab
(
Enrolled
CourseViewType
):
class
OpenEndedGradingTab
(
Enrolled
Tab
):
"""
"""
A tab for open ended grading.
A tab for open ended grading.
"""
"""
nam
e
=
'open_ended'
typ
e
=
'open_ended'
# Translators: "Open Ended Panel" appears on a tab that, when clicked, opens up a panel that
# Translators: "Open Ended Panel" appears on a tab that, when clicked, opens up a panel that
# displays information about open-ended problems that a user has submitted or needs to grade
# displays information about open-ended problems that a user has submitted or needs to grade
title
=
_
(
"Open Ended Panel"
)
title
=
_
(
"Open Ended Panel"
)
...
...
lms/djangoapps/teams/plugins.py
View file @
7461a2fd
...
@@ -3,16 +3,16 @@ Definition of the course team feature.
...
@@ -3,16 +3,16 @@ Definition of the course team feature.
"""
"""
from
django.utils.translation
import
ugettext
as
_
from
django.utils.translation
import
ugettext
as
_
from
courseware.tabs
import
Enrolled
CourseViewType
from
courseware.tabs
import
Enrolled
Tab
from
.views
import
is_feature_enabled
from
.views
import
is_feature_enabled
class
Teams
CourseViewType
(
EnrolledCourseViewType
):
class
Teams
Tab
(
EnrolledTab
):
"""
"""
The representation of the course teams view type.
The representation of the course teams view type.
"""
"""
nam
e
=
"teams"
typ
e
=
"teams"
title
=
_
(
"Teams"
)
title
=
_
(
"Teams"
)
view_name
=
"teams_dashboard"
view_name
=
"teams_dashboard"
...
@@ -24,7 +24,7 @@ class TeamsCourseViewType(EnrolledCourseViewType):
...
@@ -24,7 +24,7 @@ class TeamsCourseViewType(EnrolledCourseViewType):
course (CourseDescriptor): the course using the feature
course (CourseDescriptor): the course using the feature
user (User): the user interacting with the course
user (User): the user interacting with the course
"""
"""
if
not
super
(
Teams
CourseViewType
,
cls
)
.
is_enabled
(
course
,
user
=
user
):
if
not
super
(
Teams
Tab
,
cls
)
.
is_enabled
(
course
,
user
=
user
):
return
False
return
False
return
is_feature_enabled
(
course
)
return
is_feature_enabled
(
course
)
openedx/core/djangoapps/course_views/__init__.py
deleted
100644 → 0
View file @
8d1651cc
openedx/core/djangoapps/course_views/course_views.py
deleted
100644 → 0
View file @
8d1651cc
"""
Tabs for courseware.
"""
from
openedx.core.lib.api.plugins
import
PluginManager
from
xmodule.tabs
import
CourseTab
_
=
lambda
text
:
text
# Stevedore extension point namespaces
COURSE_VIEW_TYPE_NAMESPACE
=
'openedx.course_view_type'
def
link_reverse_func
(
reverse_name
):
"""
Returns a function that takes in a course and reverse_url_func,
and calls the reverse_url_func with the given reverse_name and course' ID.
"""
return
lambda
course
,
reverse_url_func
:
reverse_url_func
(
reverse_name
,
args
=
[
course
.
id
.
to_deprecated_string
()])
class
CourseViewType
(
object
):
"""
Base class of all course view type plugins.
These are responsible for defining tabs that can be displayed in the courseware. In order to create
and register a new CourseViewType. Create a class (either in edx-platform or in a pip installable library)
that inherits from CourseViewType and create a new entry in setup.py.
For example:
entry_points={
"openedx.course_view_type": [
"new_view = my_feature.NewCourseViewType",
],
}
"""
name
=
None
# The name of the view type, which is used for persistence and view type lookup
title
=
None
# The title of the view, which should be internationalized
priority
=
None
# The relative priority of this view that affects the ordering (lower numbers shown first)
view_name
=
None
# The name of the Django view to show this view
tab_id
=
None
# The id to be used to show a tab for this view
is_movable
=
True
# True if this course view can be moved
is_dynamic
=
False
# True if this course view is dynamically added to the list of tabs
is_default
=
True
# True if this course view is a default for the course (when enabled)
is_hideable
=
False
# True if this course view's visibility can be toggled by the author
allow_multiple
=
False
# True if this tab can be included more than once for a course.
@classmethod
def
is_enabled
(
cls
,
course
,
user
=
None
):
# pylint: disable=unused-argument
"""Returns true if this course view is enabled in the course.
Args:
course (CourseDescriptor): the course using the feature
user (User): an optional user interacting with the course (defaults to None)
"""
raise
NotImplementedError
()
@classmethod
def
validate
(
cls
,
tab_dict
,
raise_error
=
True
):
# pylint: disable=unused-argument
"""
Validates the given dict-type `tab_dict` object to ensure it contains the expected keys.
This method should be overridden by subclasses that require certain keys to be persisted in the tab.
"""
return
True
@classmethod
def
create_tab
(
cls
,
tab_dict
):
"""
Returns the tab that will be shown to represent an instance of a view.
"""
return
CourseViewTab
(
cls
,
tab_dict
=
tab_dict
)
class
CourseViewTypeManager
(
PluginManager
):
"""
Manager for all of the course view types that have been made available.
All course view types should implement `CourseViewType`.
"""
NAMESPACE
=
COURSE_VIEW_TYPE_NAMESPACE
@classmethod
def
get_course_view_types
(
cls
):
"""
Returns the list of available course view types in their canonical order.
"""
def
compare_course_view_types
(
first_type
,
second_type
):
"""Compares two course view types, for use in sorting."""
first_priority
=
first_type
.
priority
second_priority
=
second_type
.
priority
if
not
first_priority
==
second_priority
:
if
not
first_priority
:
return
1
elif
not
second_priority
:
return
-
1
else
:
return
first_priority
-
second_priority
first_name
=
first_type
.
name
second_name
=
second_type
.
name
if
first_name
<
second_name
:
return
-
1
elif
first_name
==
second_name
:
return
0
else
:
return
1
course_view_types
=
cls
.
get_available_plugins
()
.
values
()
course_view_types
.
sort
(
cmp
=
compare_course_view_types
)
return
course_view_types
class
CourseViewTab
(
CourseTab
):
"""
A tab that renders a course view.
"""
def
__init__
(
self
,
course_view_type
,
tab_dict
=
None
):
super
(
CourseViewTab
,
self
)
.
__init__
(
name
=
tab_dict
.
get
(
'name'
,
course_view_type
.
title
)
if
tab_dict
else
course_view_type
.
title
,
tab_id
=
course_view_type
.
tab_id
if
course_view_type
.
tab_id
else
course_view_type
.
name
,
link_func
=
link_reverse_func
(
course_view_type
.
view_name
),
)
self
.
type
=
course_view_type
.
name
self
.
course_view_type
=
course_view_type
self
.
is_hideable
=
course_view_type
.
is_hideable
self
.
is_hidden
=
tab_dict
.
get
(
'is_hidden'
,
False
)
if
tab_dict
else
False
self
.
is_collection
=
course_view_type
.
is_collection
if
hasattr
(
course_view_type
,
'is_collection'
)
else
False
self
.
is_movable
=
course_view_type
.
is_movable
def
is_enabled
(
self
,
course
,
user
=
None
):
""" Returns True if the tab has been enabled for this course and this user, False otherwise. """
if
not
super
(
CourseViewTab
,
self
)
.
is_enabled
(
course
,
user
=
user
):
return
False
return
self
.
course_view_type
.
is_enabled
(
course
,
user
=
user
)
def
__getitem__
(
self
,
key
):
if
key
==
'is_hidden'
:
return
self
.
is_hidden
else
:
return
super
(
CourseViewTab
,
self
)
.
__getitem__
(
key
)
def
__setitem__
(
self
,
key
,
value
):
if
key
==
'is_hidden'
:
self
.
is_hidden
=
value
else
:
super
(
CourseViewTab
,
self
)
.
__setitem__
(
key
,
value
)
def
to_json
(
self
):
""" Return a dictionary representation of this tab. """
to_json_val
=
super
(
CourseViewTab
,
self
)
.
to_json
()
if
self
.
is_hidden
:
to_json_val
.
update
({
'is_hidden'
:
True
})
return
to_json_val
def
items
(
self
,
course
):
""" If this tab is a collection, this will fetch the items in the collection. """
for
item
in
self
.
course_view_type
.
items
(
course
):
yield
item
class
StaticTab
(
CourseTab
):
"""
A custom tab.
"""
type
=
'static_tab'
def
__init__
(
self
,
tab_dict
=
None
,
name
=
None
,
url_slug
=
None
):
def
link_func
(
course
,
reverse_func
):
""" Returns a url for a given course and reverse function. """
return
reverse_func
(
self
.
type
,
args
=
[
course
.
id
.
to_deprecated_string
(),
self
.
url_slug
])
self
.
url_slug
=
tab_dict
[
'url_slug'
]
if
tab_dict
else
url_slug
super
(
StaticTab
,
self
)
.
__init__
(
name
=
tab_dict
[
'name'
]
if
tab_dict
else
name
,
tab_id
=
'static_tab_{0}'
.
format
(
self
.
url_slug
),
link_func
=
link_func
,
)
def
__getitem__
(
self
,
key
):
if
key
==
'url_slug'
:
return
self
.
url_slug
else
:
return
super
(
StaticTab
,
self
)
.
__getitem__
(
key
)
def
__setitem__
(
self
,
key
,
value
):
if
key
==
'url_slug'
:
self
.
url_slug
=
value
else
:
super
(
StaticTab
,
self
)
.
__setitem__
(
key
,
value
)
def
to_json
(
self
):
""" Return a dictionary representation of this tab. """
to_json_val
=
super
(
StaticTab
,
self
)
.
to_json
()
to_json_val
.
update
({
'url_slug'
:
self
.
url_slug
})
return
to_json_val
def
__eq__
(
self
,
other
):
if
not
super
(
StaticTab
,
self
)
.
__eq__
(
other
):
return
False
return
self
.
url_slug
==
other
.
get
(
'url_slug'
)
openedx/core/djangoapps/course_views/tests/__init__.py
deleted
100644 → 0
View file @
8d1651cc
openedx/core/lib/course_tabs.py
0 → 100644
View file @
7461a2fd
"""
Tabs for courseware.
"""
from
openedx.core.lib.api.plugins
import
PluginManager
_
=
lambda
text
:
text
# Stevedore extension point namespaces
COURSE_TAB_NAMESPACE
=
'openedx.course_tab'
class
CourseTabPluginManager
(
PluginManager
):
"""
Manager for all of the course tabs that have been made available.
All course tabs should implement `CourseTab`.
"""
NAMESPACE
=
COURSE_TAB_NAMESPACE
@classmethod
def
get_tab_types
(
cls
):
"""
Returns the list of available course tabs in their canonical order.
"""
def
compare_tabs
(
first_type
,
second_type
):
"""Compares two course tabs, for use in sorting."""
first_priority
=
first_type
.
priority
second_priority
=
second_type
.
priority
if
first_priority
!=
second_priority
:
if
first_priority
is
None
:
return
1
elif
second_priority
is
None
:
return
-
1
else
:
return
first_priority
-
second_priority
first_type
=
first_type
.
type
second_type
=
second_type
.
type
if
first_type
<
second_type
:
return
-
1
elif
first_type
==
second_type
:
return
0
else
:
return
1
tab_types
=
cls
.
get_available_plugins
()
.
values
()
tab_types
.
sort
(
cmp
=
compare_tabs
)
return
tab_types
openedx/core/
djangoapps/course_views/tests/test
_api.py
→
openedx/core/
lib/tests/test_course_tab
_api.py
View file @
7461a2fd
...
@@ -5,7 +5,7 @@ Tests for the plugin API
...
@@ -5,7 +5,7 @@ Tests for the plugin API
from
django.test
import
TestCase
from
django.test
import
TestCase
from
openedx.core.lib.api.plugins
import
PluginError
from
openedx.core.lib.api.plugins
import
PluginError
from
openedx.core.
djangoapps.course_views.course_views
import
CourseViewType
Manager
from
openedx.core.
lib.course_tabs
import
CourseTabPlugin
Manager
class
TestPluginApi
(
TestCase
):
class
TestPluginApi
(
TestCase
):
...
@@ -17,8 +17,8 @@ class TestPluginApi(TestCase):
...
@@ -17,8 +17,8 @@ class TestPluginApi(TestCase):
"""
"""
Verify that get_plugin works as expected.
Verify that get_plugin works as expected.
"""
"""
course_view_type
=
CourseViewType
Manager
.
get_plugin
(
"instructor"
)
tab_type
=
CourseTabPlugin
Manager
.
get_plugin
(
"instructor"
)
self
.
assertEqual
(
course_view
_type
.
title
,
"Instructor"
)
self
.
assertEqual
(
tab
_type
.
title
,
"Instructor"
)
with
self
.
assertRaises
(
PluginError
):
with
self
.
assertRaises
(
PluginError
):
Course
ViewType
Manager
.
get_plugin
(
"no_such_type"
)
Course
TabPlugin
Manager
.
get_plugin
(
"no_such_type"
)
openedx/core/
djangoapps/course_views/tests/test_course_view
s.py
→
openedx/core/
lib/tests/test_course_tab
s.py
View file @
7461a2fd
...
@@ -5,34 +5,34 @@ from unittest import TestCase
...
@@ -5,34 +5,34 @@ from unittest import TestCase
import
xmodule.tabs
as
xmodule_tabs
import
xmodule.tabs
as
xmodule_tabs
from
openedx.core.
djangoapps.course_views.course_views
import
CourseViewType
Manager
from
openedx.core.
lib.course_tabs
import
CourseTabPlugin
Manager
class
Course
ViewType
ManagerTestCase
(
TestCase
):
class
Course
TabPlugin
ManagerTestCase
(
TestCase
):
"""Test cases for Course
ViewType
Manager class"""
"""Test cases for Course
TabPlugin
Manager class"""
@patch
(
'openedx.core.
djangoapps.course_views.course_views.CourseViewType
Manager.get_available_plugins'
)
@patch
(
'openedx.core.
lib.course_tabs.CourseTabPlugin
Manager.get_available_plugins'
)
def
test_get_
course_view
_types
(
self
,
get_available_plugins
):
def
test_get_
tab
_types
(
self
,
get_available_plugins
):
"""
"""
Verify that get_course_view_types sorts appropriately
Verify that get_course_view_types sorts appropriately
"""
"""
def
create_mock_plugin
(
nam
e
,
priority
):
def
create_mock_plugin
(
tab_typ
e
,
priority
):
""" Create a mock plugin with the specified name and priority. """
""" Create a mock plugin with the specified name and priority. """
mock_plugin
=
Mock
()
mock_plugin
=
Mock
()
mock_plugin
.
name
=
nam
e
mock_plugin
.
type
=
tab_typ
e
mock_plugin
.
priority
=
priority
mock_plugin
.
priority
=
priority
return
mock_plugin
return
mock_plugin
mock_plugins
=
{
mock_plugins
=
{
"Last"
:
create_mock_plugin
(
nam
e
=
"Last"
,
priority
=
None
),
"Last"
:
create_mock_plugin
(
tab_typ
e
=
"Last"
,
priority
=
None
),
"Duplicate1"
:
create_mock_plugin
(
nam
e
=
"Duplicate"
,
priority
=
None
),
"Duplicate1"
:
create_mock_plugin
(
tab_typ
e
=
"Duplicate"
,
priority
=
None
),
"Duplicate2"
:
create_mock_plugin
(
nam
e
=
"Duplicate"
,
priority
=
None
),
"Duplicate2"
:
create_mock_plugin
(
tab_typ
e
=
"Duplicate"
,
priority
=
None
),
"First"
:
create_mock_plugin
(
nam
e
=
"First"
,
priority
=
1
),
"First"
:
create_mock_plugin
(
tab_typ
e
=
"First"
,
priority
=
1
),
"Second"
:
create_mock_plugin
(
nam
e
=
"Second"
,
priority
=
1
),
"Second"
:
create_mock_plugin
(
tab_typ
e
=
"Second"
,
priority
=
1
),
"Third"
:
create_mock_plugin
(
nam
e
=
"Third"
,
priority
=
3
),
"Third"
:
create_mock_plugin
(
tab_typ
e
=
"Third"
,
priority
=
3
),
}
}
get_available_plugins
.
return_value
=
mock_plugins
get_available_plugins
.
return_value
=
mock_plugins
self
.
assertEqual
(
self
.
assertEqual
(
[
plugin
.
name
for
plugin
in
CourseViewTypeManager
.
get_course_view
_types
()],
[
plugin
.
type
for
plugin
in
CourseTabPluginManager
.
get_tab
_types
()],
[
"First"
,
"Second"
,
"Third"
,
"Duplicate"
,
"Duplicate"
,
"Last"
]
[
"First"
,
"Second"
,
"Third"
,
"Duplicate"
,
"Duplicate"
,
"Last"
]
)
)
...
...
setup.py
View file @
7461a2fd
...
@@ -6,7 +6,7 @@ from setuptools import setup
...
@@ -6,7 +6,7 @@ from setuptools import setup
setup
(
setup
(
name
=
"Open edX"
,
name
=
"Open edX"
,
version
=
"0.
3
"
,
version
=
"0.
4
"
,
install_requires
=
[
"distribute"
],
install_requires
=
[
"distribute"
],
requires
=
[],
requires
=
[],
# NOTE: These are not the names we should be installing. This tree should
# NOTE: These are not the names we should be installing. This tree should
...
@@ -18,24 +18,24 @@ setup(
...
@@ -18,24 +18,24 @@ setup(
"cms"
,
"cms"
,
],
],
entry_points
=
{
entry_points
=
{
"openedx.course_
view_type
"
:
[
"openedx.course_
tab
"
:
[
"ccx = lms.djangoapps.ccx.plugins:CcxCourse
ViewType
"
,
"ccx = lms.djangoapps.ccx.plugins:CcxCourse
Tab
"
,
"courseware = lms.djangoapps.courseware.tabs:Courseware
ViewType
"
,
"courseware = lms.djangoapps.courseware.tabs:Courseware
Tab
"
,
"course_info = lms.djangoapps.courseware.tabs:CourseInfo
ViewType
"
,
"course_info = lms.djangoapps.courseware.tabs:CourseInfo
Tab
"
,
"discussion = lms.djangoapps.django_comment_client.forum.views:Discussion
CourseViewType
"
,
"discussion = lms.djangoapps.django_comment_client.forum.views:Discussion
Tab
"
,
"edxnotes = lms.djangoapps.edxnotes.plugins:EdxNotes
CourseViewType
"
,
"edxnotes = lms.djangoapps.edxnotes.plugins:EdxNotes
Tab
"
,
"external_discussion = lms.djangoapps.courseware.tabs:ExternalDiscussionCourse
ViewType
"
,
"external_discussion = lms.djangoapps.courseware.tabs:ExternalDiscussionCourse
Tab
"
,
"external_link = lms.djangoapps.courseware.tabs:ExternalLinkCourse
ViewType
"
,
"external_link = lms.djangoapps.courseware.tabs:ExternalLinkCourse
Tab
"
,
"html_textbooks = lms.djangoapps.courseware.tabs:HtmlTextbook
CourseView
s"
,
"html_textbooks = lms.djangoapps.courseware.tabs:HtmlTextbook
Tab
s"
,
"instructor = lms.djangoapps.instructor.views.instructor_dashboard:InstructorDashboard
ViewType
"
,
"instructor = lms.djangoapps.instructor.views.instructor_dashboard:InstructorDashboard
Tab
"
,
"notes = lms.djangoapps.notes.views:Notes
CourseViewType
"
,
"notes = lms.djangoapps.notes.views:Notes
Tab
"
,
"pdf_textbooks = lms.djangoapps.courseware.tabs:PDFTextbook
CourseView
s"
,
"pdf_textbooks = lms.djangoapps.courseware.tabs:PDFTextbook
Tab
s"
,
"progress = lms.djangoapps.courseware.tabs:Progress
CourseViewType
"
,
"progress = lms.djangoapps.courseware.tabs:Progress
Tab
"
,
"static_tab =
lms.djangoapps.courseware.tabs:StaticCourseViewType
"
,
"static_tab =
xmodule.tabs:StaticTab
"
,
"syllabus = lms.djangoapps.courseware.tabs:Syllabus
CourseViewType
"
,
"syllabus = lms.djangoapps.courseware.tabs:Syllabus
Tab
"
,
"teams = lms.djangoapps.teams.plugins:Teams
CourseViewType
"
,
"teams = lms.djangoapps.teams.plugins:Teams
Tab
"
,
"textbooks = lms.djangoapps.courseware.tabs:Textbook
CourseView
s"
,
"textbooks = lms.djangoapps.courseware.tabs:Textbook
Tab
s"
,
"wiki = lms.djangoapps.course_wiki.tab:Wiki
CourseViewType
"
,
"wiki = lms.djangoapps.course_wiki.tab:Wiki
Tab
"
,
# ORA 1 tabs (deprecated)
# ORA 1 tabs (deprecated)
"peer_grading = lms.djangoapps.open_ended_grading.views:PeerGradingTab"
,
"peer_grading = lms.djangoapps.open_ended_grading.views:PeerGradingTab"
,
...
...
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