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
89f93df4
Commit
89f93df4
authored
Jul 01, 2016
by
Andy Armstrong
Committed by
Brian Jacobel
Aug 17, 2016
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor out a new discussions Django app
parent
c4e5b201
Show whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
376 additions
and
227 deletions
+376
-227
common/test/acceptance/tests/discussion/test_discussion.py
+1
-1
lms/djangoapps/courseware/tests/test_tabs.py
+1
-1
lms/djangoapps/discussion/__init__.py
+0
-0
lms/djangoapps/discussion/plugins.py
+28
-0
lms/djangoapps/discussion/static/discussion/js/discussion_board_factory.js
+36
-0
lms/djangoapps/discussion/static/discussion/js/discussion_profile_page_factory.js
+29
-0
lms/djangoapps/discussion/static/discussion/js/discussion_router.js
+0
-0
lms/djangoapps/discussion/static/discussion/js/spec/views/discussion_user_profile_view_spec.js
+0
-0
lms/djangoapps/discussion/static/discussion/js/views/discussion_user_profile_view.js
+0
-0
lms/djangoapps/discussion/templates/discussion/discussion_board.html
+17
-4
lms/djangoapps/discussion/templates/discussion/discussion_profile_page.html
+0
-0
lms/djangoapps/discussion/templates/discussion/maintenance.html
+0
-0
lms/djangoapps/discussion/templates/discussion/user_profile.html
+62
-0
lms/djangoapps/discussion/tests/test_views.py
+7
-7
lms/djangoapps/discussion/urls.py
+1
-1
lms/djangoapps/discussion/views.py
+14
-30
lms/djangoapps/django_comment_client/urls.py
+0
-1
lms/djangoapps/django_comment_client/utils.py
+2
-2
lms/envs/common.py
+3
-1
lms/static/lms/js/build.js
+1
-0
lms/static/sass/discussion/_build.scss
+1
-0
lms/static/sass/discussion/_discussion.scss
+0
-157
lms/static/sass/discussion/_layouts.scss
+40
-0
lms/static/sass/discussion/views/_thread.scss
+124
-20
lms/static/sass/shared-v2/_layouts.scss
+1
-0
lms/urls.py
+6
-0
setup.py
+2
-2
No files found.
common/test/acceptance/tests/discussion/test_discussion.py
View file @
89f93df4
...
@@ -1037,7 +1037,7 @@ class DiscussionUserProfileTest(UniqueCourseTest):
...
@@ -1037,7 +1037,7 @@ class DiscussionUserProfileTest(UniqueCourseTest):
Tests for user profile page in discussion tab.
Tests for user profile page in discussion tab.
"""
"""
PAGE_SIZE
=
20
# d
jango_comment_client.forum
.views.THREADS_PER_PAGE
PAGE_SIZE
=
20
# d
iscussion
.views.THREADS_PER_PAGE
PROFILED_USERNAME
=
"profiled-user"
PROFILED_USERNAME
=
"profiled-user"
def
setUp
(
self
):
def
setUp
(
self
):
...
...
lms/djangoapps/courseware/tests/test_tabs.py
View file @
89f93df4
...
@@ -754,7 +754,7 @@ class DiscussionLinkTestCase(TabTestCase):
...
@@ -754,7 +754,7 @@ class DiscussionLinkTestCase(TabTestCase):
"""Custom reverse function"""
"""Custom reverse function"""
def
reverse_discussion_link
(
viewname
,
args
):
def
reverse_discussion_link
(
viewname
,
args
):
"""reverse lookup for discussion link"""
"""reverse lookup for discussion link"""
if
viewname
==
"d
jango_comment_client.forum
.views.forum_form_discussion"
and
args
==
[
unicode
(
course
.
id
)]:
if
viewname
==
"d
iscussion
.views.forum_form_discussion"
and
args
==
[
unicode
(
course
.
id
)]:
return
"default_discussion_link"
return
"default_discussion_link"
return
reverse_discussion_link
return
reverse_discussion_link
...
...
lms/djangoapps/d
jango_comment_client/forum
/__init__.py
→
lms/djangoapps/d
iscussion
/__init__.py
View file @
89f93df4
File moved
lms/djangoapps/discussion/plugins.py
0 → 100644
View file @
89f93df4
"""
Views handling read (GET) requests for the Discussion tab and inline discussions.
"""
from
django.conf
import
settings
from
django.utils.translation
import
ugettext_noop
from
courseware.tabs
import
EnrolledTab
import
django_comment_client.utils
as
utils
class
DiscussionTab
(
EnrolledTab
):
"""
A tab for the cs_comments_service forums.
"""
type
=
'discussion'
title
=
ugettext_noop
(
'Discussion'
)
priority
=
None
view_name
=
'discussion.views.forum_form_discussion'
is_hideable
=
settings
.
FEATURES
.
get
(
'ALLOW_HIDING_DISCUSSION_TAB'
,
False
)
is_default
=
False
@classmethod
def
is_enabled
(
cls
,
course
,
user
=
None
):
if
not
super
(
DiscussionTab
,
cls
)
.
is_enabled
(
course
,
user
):
return
False
return
utils
.
is_discussion_enabled
(
course
.
id
)
lms/djangoapps/discussion/static/discussion/js/discussion_board_factory.js
0 → 100644
View file @
89f93df4
;(
function
(
define
)
{
'use strict'
;
define
([
'jquery'
,
'backbone'
],
function
(
$
,
Backbone
)
{
return
function
(
options
)
{
var
element
=
options
.
el
,
userInfo
=
element
.
data
(
'user-info'
),
sortPreference
=
element
.
data
(
'sort-preference'
),
threads
=
element
.
data
(
'threads'
),
threadPages
=
element
.
data
(
'thread-pages'
),
contentInfo
=
element
.
data
(
'content-info'
),
user
=
new
window
.
DiscussionUser
(
userInfo
),
discussion
,
courseSettings
;
// TODO: Perhaps eliminate usage of global variables when possible
window
.
DiscussionUtil
.
loadRolesFromContainer
();
window
.
$$course_id
=
options
.
courseId
;
window
.
courseName
=
element
.
data
(
'course-name'
);
window
.
DiscussionUtil
.
setUser
(
user
);
window
.
user
=
user
;
window
.
Content
.
loadContentInfos
(
contentInfo
);
discussion
=
new
window
.
Discussion
(
threads
,
{
pages
:
threadPages
,
sort
:
sortPreference
});
courseSettings
=
new
window
.
DiscussionCourseSettings
(
element
.
data
(
'course-settings'
));
// jshint nonew:false
new
window
.
DiscussionRouter
({
discussion
:
discussion
,
course_settings
:
courseSettings
});
Backbone
.
history
.
start
({
pushState
:
true
,
root
:
'/courses/'
+
options
.
courseId
+
'/discussion/forum/'
});
};
});
}).
call
(
this
,
define
||
RequireJS
.
define
);
lms/djangoapps/discussion/static/discussion/js/discussion_profile_page_factory.js
0 → 100644
View file @
89f93df4
;(
function
(
define
)
{
'use strict'
;
define
([
'jquery'
,
'DiscussionUserProfileView'
],
function
(
$
,
DiscussionUserProfileView
)
{
return
function
(
options
)
{
var
element
=
options
.
el
,
threads
=
element
.
data
(
'threads'
),
userInfo
=
element
.
data
(
'user-info'
),
page
=
element
.
data
(
'page'
),
numPages
=
element
.
data
(
'num-pages'
);
// Roles are not included in user profile page, but they are not used for anything
window
.
DiscussionUtil
.
loadRoles
({
'Moderator'
:
[],
'Administrator'
:
[],
'Community TA'
:
[]
});
window
.
$$course_id
=
element
.
data
(
'course-id'
);
window
.
user
=
new
window
.
DiscussionUser
(
userInfo
);
// jshint nonew:false
new
DiscussionUserProfileView
({
el
:
element
,
collection
:
threads
,
page
:
page
,
numPages
:
numPages
});
};
});
}).
call
(
this
,
define
||
RequireJS
.
define
);
common/static/common/js/discussion
/discussion_router.js
→
lms/djangoapps/discussion/static/discussion/js
/discussion_router.js
View file @
89f93df4
File moved
common/static/common/js/spec/discussion/view
/discussion_user_profile_view_spec.js
→
lms/djangoapps/discussion/static/discussion/js/spec/views
/discussion_user_profile_view_spec.js
View file @
89f93df4
File moved
common/static/common/js/discussion
/views/discussion_user_profile_view.js
→
lms/djangoapps/discussion/static/discussion/js
/views/discussion_user_profile_view.js
View file @
89f93df4
File moved
lms/
templates/discussion/index
.html
→
lms/
djangoapps/discussion/templates/discussion/discussion_board
.html
View file @
89f93df4
...
@@ -9,25 +9,38 @@
...
@@ -9,25 +9,38 @@
from
django
.
utils
.
translation
import
ugettext
as
_
from
django
.
utils
.
translation
import
ugettext
as
_
from
django
.
template
.
defaultfilters
import
escapejs
from
django
.
template
.
defaultfilters
import
escapejs
from
django
.
core
.
urlresolvers
import
reverse
from
django
.
core
.
urlresolvers
import
reverse
from
openedx
.
core
.
djangolib
.
js_utils
import
(
dump_js_escaped_json
,
js_escaped_string
)
%
>
%
>
<
%
block
name=
"bodyclass"
>
discussion
</
%
block>
<
%
block
name=
"bodyclass"
>
discussion
</
%
block>
<
%
block
name=
"pagetitle"
>
${_("Discussion - {course_number}").format(course_number=course.display_number_with_default)}
</
%
block>
<
%
block
name=
"pagetitle"
>
${_("Discussion - {course_number}").format(course_number=course.display_number_with_default)}
</
%
block>
<
%
block
name=
"headextra"
>
<
%
block
name=
"headextra"
>
<
%
include
file=
"_js_head_dependencies.html"
/>
<
%
include
file=
"
../discussion/
_js_head_dependencies.html"
/>
</
%
block>
</
%
block>
<
%
block
name=
"js_extra"
>
<
%
block
name=
"js_extra"
>
## Enable fast preview to fix discussion MathJax rendering bug when page first loads.
## Enable fast preview to fix discussion MathJax rendering bug when page first loads.
<
%
include
file=
"_js_body_dependencies.html"
args=
"disable_fast_preview=False"
/>
<
%
include
file=
"
/discussion/
_js_body_dependencies.html"
args=
"disable_fast_preview=False"
/>
<
%
static:js
group=
'discussion'
/>
<
%
static:js
group=
'discussion'
/>
<script
type=
"text/javascript"
>
RequireJS
.
define
(
'DiscussionBoardFactory'
,
[],
function
()
{
return
window
[
'DiscussionBoardFactory'
];});
</script>
<
%
static:require_module
module_name=
"discussion/js/discussion_board_factory"
class_name=
"DiscussionBoardFactory"
>
DiscussionBoardFactory({
courseId: '${unicode(course.id) | n, js_escaped_string}',
el: $(".discussion-board")
});
</
%
static:require
_module
>
</
%
block>
</
%
block>
<
%
include
file=
"_discussion_course_navigation.html"
args=
"active_page='discussion'"
/>
<
%
include
file=
"
/discussion/
_discussion_course_navigation.html"
args=
"active_page='discussion'"
/>
<
%
block
name=
"content"
>
<
%
block
name=
"content"
>
<section
class=
"discussion container"
id=
"discussion-container"
<section
class=
"discussion
discussion-board
container"
id=
"discussion-container"
data-roles=
"${roles}"
data-roles=
"${roles}"
data-course-id=
"${course_id}"
data-course-id=
"${course_id}"
data-course-name=
"${course.display_name_with_default}"
data-course-name=
"${course.display_name_with_default}"
...
...
lms/
templates/discussion/user_profil
e.html
→
lms/
djangoapps/discussion/templates/discussion/discussion_profile_pag
e.html
View file @
89f93df4
File moved
lms/templates/discussion/maintenance.html
→
lms/
djangoapps/discussion/
templates/discussion/maintenance.html
View file @
89f93df4
File moved
lms/djangoapps/discussion/templates/discussion/user_profile.html
0 → 100644
View file @
89f93df4
## mako
<
%!
main_css =
"style-discussion-main"
%
>
<
%
page
expression_filter=
"h"
/>
<
%
inherit
file=
"../main.html"
/>
<
%
namespace
name=
'static'
file=
'../static_content.html'
/>
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
from
django
.
template
.
defaultfilters
import
escapejs
from
openedx
.
core
.
djangolib
.
js_utils
import
(
dump_js_escaped_json
,
js_escaped_string
)
%
>
<
%
block
name=
"bodyclass"
>
discussion-user-profile
</
%
block>
<
%
block
name=
"pagetitle"
>
${_("Discussion - {course_number}").format(course_number=course.display_number_with_default)}
</
%
block>
<
%
block
name=
"headextra"
>
<
%
include
file=
"_js_head_dependencies.html"
/>
</
%
block>
<
%
block
name=
"js_extra"
>
<
%
include
file=
"_js_body_dependencies.html"
/>
<
%
static:js
group=
'discussion'
/>
<script
type=
"text/javascript"
>
RequireJS
.
define
(
'DiscussionUserProfileView'
,
[],
function
()
{
return
window
[
'DiscussionUserProfileView'
];});
</script>
<
%
static:require_module
module_name=
"discussion/js/discussion_profile_page_factory"
class_name=
"DiscussionProfilePageFactory"
>
DiscussionProfilePageFactory({
courseId: '${unicode(course.id) | n, js_escaped_string}',
el: $(".discussion-user-threads")
});
</
%
static:require
_module
>
</
%
block>
<
%
include
file=
"../courseware/course_navigation.html"
args=
"active_page='discussion'"
/>
<section
class=
"container"
>
<div
class=
"forum-nav"
>
<section
class=
"user-profile"
>
<nav
aria-label=
"${_('User Profile')}"
>
<article
class=
"sidebar-module discussion-sidebar"
>
<
%
include
file=
"_user_profile.html"
/>
</article>
</nav>
</section>
</div>
<div
class=
"discussion-column"
id=
"discussion-column"
>
<main
id=
"main"
aria-label=
"Content"
tabindex=
"-1"
>
<section
class=
"course-content container discussion-user-threads"
data-course-id=
"${course.id}"
data-course-name=
"${course.display_name_with_default}"
data-threads=
"${threads}"
data-user-info=
"${user_info}"
data-page=
"${page}"
data-num-pages=
"${num_pages}"
/>
</main>
</div>
</section>
<
%
include
file=
"_underscore_templates.html"
/>
lms/djangoapps/d
jango_comment_client/forum/test
s.py
→
lms/djangoapps/d
iscussion/tests/test_view
s.py
View file @
89f93df4
...
@@ -10,7 +10,6 @@ from django.utils import translation
...
@@ -10,7 +10,6 @@ from django.utils import translation
from
lms.lib.comment_client.utils
import
CommentClientPaginatedResult
from
lms.lib.comment_client.utils
import
CommentClientPaginatedResult
from
django_comment_common.utils
import
ThreadContext
from
django_comment_common.utils
import
ThreadContext
from
django_comment_client.forum
import
views
from
django_comment_client.permissions
import
get_team
from
django_comment_client.permissions
import
get_team
from
django_comment_client.tests.group_id
import
(
from
django_comment_client.tests.group_id
import
(
CohortedTopicGroupIdTestMixin
,
CohortedTopicGroupIdTestMixin
,
...
@@ -19,6 +18,7 @@ from django_comment_client.tests.group_id import (
...
@@ -19,6 +18,7 @@ from django_comment_client.tests.group_id import (
from
django_comment_client.tests.unicode
import
UnicodeTestMixin
from
django_comment_client.tests.unicode
import
UnicodeTestMixin
from
django_comment_client.tests.utils
import
CohortedTestCase
from
django_comment_client.tests.utils
import
CohortedTestCase
from
django_comment_client.utils
import
strip_none
from
django_comment_client.utils
import
strip_none
from
lms.djangoapps.discussion
import
views
from
student.tests.factories
import
UserFactory
,
CourseEnrollmentFactory
from
student.tests.factories
import
UserFactory
,
CourseEnrollmentFactory
from
util.testing
import
UrlResetMixin
from
util.testing
import
UrlResetMixin
from
openedx.core.djangoapps.util.testing
import
ContentGroupTestCase
from
openedx.core.djangoapps.util.testing
import
ContentGroupTestCase
...
@@ -87,7 +87,7 @@ class ViewsExceptionTestCase(UrlResetMixin, ModuleStoreTestCase):
...
@@ -87,7 +87,7 @@ class ViewsExceptionTestCase(UrlResetMixin, ModuleStoreTestCase):
# that gets the current user's info
# that gets the current user's info
mock_from_django_user
.
return_value
=
Mock
()
mock_from_django_user
.
return_value
=
Mock
()
url
=
reverse
(
'd
jango_comment_client.forum
.views.user_profile'
,
url
=
reverse
(
'd
iscussion
.views.user_profile'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
(),
'user_id'
:
'12345'
})
# There is no user 12345
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
(),
'user_id'
:
'12345'
})
# There is no user 12345
self
.
response
=
self
.
client
.
get
(
url
)
self
.
response
=
self
.
client
.
get
(
url
)
self
.
assertEqual
(
self
.
response
.
status_code
,
404
)
self
.
assertEqual
(
self
.
response
.
status_code
,
404
)
...
@@ -104,7 +104,7 @@ class ViewsExceptionTestCase(UrlResetMixin, ModuleStoreTestCase):
...
@@ -104,7 +104,7 @@ class ViewsExceptionTestCase(UrlResetMixin, ModuleStoreTestCase):
# that gets the current user's info
# that gets the current user's info
mock_from_django_user
.
return_value
=
Mock
()
mock_from_django_user
.
return_value
=
Mock
()
url
=
reverse
(
'd
jango_comment_client.forum
.views.followed_threads'
,
url
=
reverse
(
'd
iscussion
.views.followed_threads'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
(),
'user_id'
:
'12345'
})
# There is no user 12345
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
(),
'user_id'
:
'12345'
})
# There is no user 12345
self
.
response
=
self
.
client
.
get
(
url
)
self
.
response
=
self
.
client
.
get
(
url
)
self
.
assertEqual
(
self
.
response
.
status_code
,
404
)
self
.
assertEqual
(
self
.
response
.
status_code
,
404
)
...
@@ -1257,7 +1257,7 @@ class CommentsServiceRequestHeadersTestCase(UrlResetMixin, ModuleStoreTestCase):
...
@@ -1257,7 +1257,7 @@ class CommentsServiceRequestHeadersTestCase(UrlResetMixin, ModuleStoreTestCase):
self
.
client
.
get
(
self
.
client
.
get
(
reverse
(
reverse
(
"d
jango_comment_client.forum
.views.single_thread"
,
"d
iscussion
.views.single_thread"
,
kwargs
=
{
kwargs
=
{
"course_id"
:
self
.
course
.
id
.
to_deprecated_string
(),
"course_id"
:
self
.
course
.
id
.
to_deprecated_string
(),
"discussion_id"
:
"dummy_discussion_id"
,
"discussion_id"
:
"dummy_discussion_id"
,
...
@@ -1274,7 +1274,7 @@ class CommentsServiceRequestHeadersTestCase(UrlResetMixin, ModuleStoreTestCase):
...
@@ -1274,7 +1274,7 @@ class CommentsServiceRequestHeadersTestCase(UrlResetMixin, ModuleStoreTestCase):
self
.
client
.
get
(
self
.
client
.
get
(
reverse
(
reverse
(
"d
jango_comment_client.forum
.views.forum_form_discussion"
,
"d
iscussion
.views.forum_form_discussion"
,
kwargs
=
{
"course_id"
:
self
.
course
.
id
.
to_deprecated_string
()}
kwargs
=
{
"course_id"
:
self
.
course
.
id
.
to_deprecated_string
()}
),
),
)
)
...
@@ -1361,7 +1361,7 @@ class ForumDiscussionXSSTestCase(UrlResetMixin, ModuleStoreTestCase):
...
@@ -1361,7 +1361,7 @@ class ForumDiscussionXSSTestCase(UrlResetMixin, ModuleStoreTestCase):
Test that XSS attack is prevented
Test that XSS attack is prevented
"""
"""
reverse_url
=
"
%
s
%
s"
%
(
reverse
(
reverse_url
=
"
%
s
%
s"
%
(
reverse
(
"d
jango_comment_client.forum
.views.forum_form_discussion"
,
"d
iscussion
.views.forum_form_discussion"
,
kwargs
=
{
"course_id"
:
unicode
(
self
.
course
.
id
)}),
'/forum_form_discussion'
)
kwargs
=
{
"course_id"
:
unicode
(
self
.
course
.
id
)}),
'/forum_form_discussion'
)
# Test that malicious code does not appear in html
# Test that malicious code does not appear in html
url
=
"
%
s?
%
s=
%
s"
%
(
reverse_url
,
'sort_key'
,
malicious_code
)
url
=
"
%
s?
%
s=
%
s"
%
(
reverse_url
,
'sort_key'
,
malicious_code
)
...
@@ -1380,7 +1380,7 @@ class ForumDiscussionXSSTestCase(UrlResetMixin, ModuleStoreTestCase):
...
@@ -1380,7 +1380,7 @@ class ForumDiscussionXSSTestCase(UrlResetMixin, ModuleStoreTestCase):
mock_from_django_user
.
return_value
=
Mock
()
mock_from_django_user
.
return_value
=
Mock
()
mock_request
.
side_effect
=
make_mock_request_impl
(
course
=
self
.
course
,
text
=
'dummy'
)
mock_request
.
side_effect
=
make_mock_request_impl
(
course
=
self
.
course
,
text
=
'dummy'
)
url
=
reverse
(
'd
jango_comment_client.forum
.views.user_profile'
,
url
=
reverse
(
'd
iscussion
.views.user_profile'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
),
'user_id'
:
str
(
self
.
student
.
id
)})
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
),
'user_id'
:
str
(
self
.
student
.
id
)})
# Test that malicious code does not appear in html
# Test that malicious code does not appear in html
url_string
=
"
%
s?
%
s=
%
s"
%
(
url
,
'page'
,
malicious_code
)
url_string
=
"
%
s?
%
s=
%
s"
%
(
url
,
'page'
,
malicious_code
)
...
...
lms/djangoapps/d
jango_comment_client/forum
/urls.py
→
lms/djangoapps/d
iscussion
/urls.py
View file @
89f93df4
...
@@ -4,7 +4,7 @@ Forum urls for the django_comment_client.
...
@@ -4,7 +4,7 @@ Forum urls for the django_comment_client.
from
django.conf.urls
import
url
,
patterns
from
django.conf.urls
import
url
,
patterns
urlpatterns
=
patterns
(
urlpatterns
=
patterns
(
'd
jango_comment_client.forum
.views'
,
'd
iscussion
.views'
,
url
(
r'users/(?P<user_id>\w+)/followed$'
,
'followed_threads'
,
name
=
'followed_threads'
),
url
(
r'users/(?P<user_id>\w+)/followed$'
,
'followed_threads'
,
name
=
'followed_threads'
),
url
(
r'users/(?P<user_id>\w+)$'
,
'user_profile'
,
name
=
'user_profile'
),
url
(
r'users/(?P<user_id>\w+)$'
,
'user_profile'
,
name
=
'user_profile'
),
...
...
lms/djangoapps/d
jango_comment_client/forum
/views.py
→
lms/djangoapps/d
iscussion
/views.py
View file @
89f93df4
...
@@ -7,23 +7,20 @@ import json
...
@@ -7,23 +7,20 @@ import json
import
logging
import
logging
from
django.contrib.auth.decorators
import
login_required
from
django.contrib.auth.decorators
import
login_required
from
django.conf
import
settings
from
django.core.context_processors
import
csrf
from
django.core.context_processors
import
csrf
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
from
django.contrib.auth.models
import
User
from
django.contrib.auth.models
import
User
from
django.http
import
Http404
,
HttpResponseBadRequest
from
django.http
import
Http404
,
HttpResponseBadRequest
from
django.
utils.translation
import
ugettext_noop
from
django.
shortcuts
import
render_to_response
from
django.views.decorators.http
import
require_GET
from
django.views.decorators.http
import
require_GET
import
newrelic.agent
import
newrelic.agent
from
edxmako.shortcuts
import
render_to_response
from
courseware.courses
import
get_course_with_access
from
courseware.courses
import
get_course_with_access
from
openedx.core.djangoapps.course_groups.cohorts
import
(
from
openedx.core.djangoapps.course_groups.cohorts
import
(
is_course_cohorted
,
is_course_cohorted
,
get_cohort_id
,
get_cohort_id
,
get_course_cohorts
,
get_course_cohorts
,
)
)
from
courseware.tabs
import
EnrolledTab
from
courseware.access
import
has_access
from
courseware.access
import
has_access
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
...
@@ -48,25 +45,6 @@ PAGES_NEARBY_DELTA = 2
...
@@ -48,25 +45,6 @@ PAGES_NEARBY_DELTA = 2
log
=
logging
.
getLogger
(
"edx.discussions"
)
log
=
logging
.
getLogger
(
"edx.discussions"
)
class
DiscussionTab
(
EnrolledTab
):
"""
A tab for the cs_comments_service forums.
"""
type
=
'discussion'
title
=
ugettext_noop
(
'Discussion'
)
priority
=
None
view_name
=
'django_comment_client.forum.views.forum_form_discussion'
is_hideable
=
settings
.
FEATURES
.
get
(
'ALLOW_HIDING_DISCUSSION_TAB'
,
False
)
is_default
=
False
@classmethod
def
is_enabled
(
cls
,
course
,
user
=
None
):
if
not
super
(
DiscussionTab
,
cls
)
.
is_enabled
(
course
,
user
):
return
False
return
utils
.
is_discussion_enabled
(
course
.
id
)
@newrelic.agent.function_trace
()
@newrelic.agent.function_trace
()
def
make_course_settings
(
course
,
user
):
def
make_course_settings
(
course
,
user
):
"""
"""
...
@@ -115,7 +93,8 @@ def get_threads(request, course, discussion_id=None, per_page=THREADS_PER_PAGE):
...
@@ -115,7 +93,8 @@ def get_threads(request, course, discussion_id=None, per_page=THREADS_PER_PAGE):
# If the user did not select a sort key, use their last used sort key
# If the user did not select a sort key, use their last used sort key
cc_user
=
cc
.
User
.
from_django_user
(
request
.
user
)
cc_user
=
cc
.
User
.
from_django_user
(
request
.
user
)
cc_user
.
retrieve
()
cc_user
.
retrieve
()
# TODO: After the comment service is updated this can just be user.default_sort_key because the service returns the default value
# TODO: After the comment service is updated this can just be
# user.default_sort_key because the service returns the default value
default_query_params
[
'sort_key'
]
=
cc_user
.
get
(
'default_sort_key'
)
or
default_query_params
[
'sort_key'
]
default_query_params
[
'sort_key'
]
=
cc_user
.
get
(
'default_sort_key'
)
or
default_query_params
[
'sort_key'
]
else
:
else
:
# If the user clicked a sort key, update their default sort key
# If the user clicked a sort key, update their default sort key
...
@@ -239,7 +218,10 @@ def forum_form_discussion(request, course_key):
...
@@ -239,7 +218,10 @@ def forum_form_discussion(request, course_key):
threads
=
[
utils
.
prepare_content
(
thread
,
course_key
,
is_staff
)
for
thread
in
unsafethreads
]
threads
=
[
utils
.
prepare_content
(
thread
,
course_key
,
is_staff
)
for
thread
in
unsafethreads
]
except
cc
.
utils
.
CommentClientMaintenanceError
:
except
cc
.
utils
.
CommentClientMaintenanceError
:
log
.
warning
(
"Forum is in maintenance mode"
)
log
.
warning
(
"Forum is in maintenance mode"
)
return
render_to_response
(
'discussion/maintenance.html'
,
{})
return
render_to_response
(
'discussion/maintenance.html'
,
{
'disable_courseware_js'
:
True
,
'uses_pattern_library'
:
True
,
})
except
ValueError
:
except
ValueError
:
return
HttpResponseBadRequest
(
"Invalid group_id"
)
return
HttpResponseBadRequest
(
"Invalid group_id"
)
...
@@ -290,7 +272,7 @@ def forum_form_discussion(request, course_key):
...
@@ -290,7 +272,7 @@ def forum_form_discussion(request, course_key):
'uses_pattern_library'
:
True
,
'uses_pattern_library'
:
True
,
}
}
# print "start rendering.."
# print "start rendering.."
return
render_to_response
(
'discussion/
index
.html'
,
context
)
return
render_to_response
(
'discussion/
discussion_board
.html'
,
context
)
@require_GET
@require_GET
...
@@ -318,8 +300,8 @@ def single_thread(request, course_key, discussion_id, thread_id):
...
@@ -318,8 +300,8 @@ def single_thread(request, course_key, discussion_id, thread_id):
response_skip
=
request
.
GET
.
get
(
"resp_skip"
),
response_skip
=
request
.
GET
.
get
(
"resp_skip"
),
response_limit
=
request
.
GET
.
get
(
"resp_limit"
)
response_limit
=
request
.
GET
.
get
(
"resp_limit"
)
)
)
except
cc
.
utils
.
CommentClientRequestError
as
e
:
except
cc
.
utils
.
CommentClientRequestError
as
e
rror
:
if
e
.
status_code
==
404
:
if
e
rror
.
status_code
==
404
:
raise
Http404
raise
Http404
raise
raise
...
@@ -404,7 +386,7 @@ def single_thread(request, course_key, discussion_id, thread_id):
...
@@ -404,7 +386,7 @@ def single_thread(request, course_key, discussion_id, thread_id):
'disable_courseware_js'
:
True
,
'disable_courseware_js'
:
True
,
'uses_pattern_library'
:
True
,
'uses_pattern_library'
:
True
,
}
}
return
render_to_response
(
'discussion/
index
.html'
,
context
)
return
render_to_response
(
'discussion/
discussion_board
.html'
,
context
)
@require_GET
@require_GET
...
@@ -465,7 +447,9 @@ def user_profile(request, course_key, user_id):
...
@@ -465,7 +447,9 @@ def user_profile(request, course_key, user_id):
'annotated_content_info'
:
json
.
dumps
(
annotated_content_info
),
'annotated_content_info'
:
json
.
dumps
(
annotated_content_info
),
'page'
:
query_params
[
'page'
],
'page'
:
query_params
[
'page'
],
'num_pages'
:
query_params
[
'num_pages'
],
'num_pages'
:
query_params
[
'num_pages'
],
'learner_profile_page_url'
:
reverse
(
'learner_profile'
,
kwargs
=
{
'username'
:
django_user
.
username
})
'learner_profile_page_url'
:
reverse
(
'learner_profile'
,
kwargs
=
{
'username'
:
django_user
.
username
}),
'disable_courseware_js'
:
True
,
'uses_pattern_library'
:
True
,
}
}
return
render_to_response
(
'discussion/user_profile.html'
,
context
)
return
render_to_response
(
'discussion/user_profile.html'
,
context
)
...
...
lms/djangoapps/django_comment_client/urls.py
View file @
89f93df4
...
@@ -6,6 +6,5 @@ from django.conf.urls import url, patterns, include
...
@@ -6,6 +6,5 @@ from django.conf.urls import url, patterns, include
urlpatterns
=
patterns
(
urlpatterns
=
patterns
(
''
,
''
,
url
(
r'forum/?'
,
include
(
'django_comment_client.forum.urls'
)),
url
(
r''
,
include
(
'django_comment_client.base.urls'
)),
url
(
r''
,
include
(
'django_comment_client.base.urls'
)),
)
)
lms/djangoapps/django_comment_client/utils.py
View file @
89f93df4
...
@@ -591,10 +591,10 @@ def permalink(content):
...
@@ -591,10 +591,10 @@ def permalink(content):
else
:
else
:
course_id
=
content
[
'course_id'
]
course_id
=
content
[
'course_id'
]
if
content
[
'type'
]
==
'thread'
:
if
content
[
'type'
]
==
'thread'
:
return
reverse
(
'd
jango_comment_client.forum
.views.single_thread'
,
return
reverse
(
'd
iscussion
.views.single_thread'
,
args
=
[
course_id
,
content
[
'commentable_id'
],
content
[
'id'
]])
args
=
[
course_id
,
content
[
'commentable_id'
],
content
[
'id'
]])
else
:
else
:
return
reverse
(
'd
jango_comment_client.forum
.views.single_thread'
,
return
reverse
(
'd
iscussion
.views.single_thread'
,
args
=
[
course_id
,
content
[
'commentable_id'
],
content
[
'thread_id'
]])
+
'#'
+
content
[
'id'
]
args
=
[
course_id
,
content
[
'commentable_id'
],
content
[
'thread_id'
]])
+
'#'
+
content
[
'id'
]
...
...
lms/envs/common.py
View file @
89f93df4
...
@@ -1941,8 +1941,10 @@ INSTALLED_APPS = (
...
@@ -1941,8 +1941,10 @@ INSTALLED_APPS = (
'django_comment_client'
,
'django_comment_client'
,
'django_comment_common'
,
'django_comment_common'
,
'discussion_api'
,
'discussion_api'
,
'
notes
'
,
'
lms.djangoapps.discussion
'
,
# Notes
'notes'
,
'edxnotes'
,
'edxnotes'
,
# Splash screen
# Splash screen
...
...
lms/static/lms/js/build.js
View file @
89f93df4
...
@@ -18,6 +18,7 @@
...
@@ -18,6 +18,7 @@
* done.
* done.
*/
*/
modules
:
getModulesList
([
modules
:
getModulesList
([
'discussion/js/discussion_board_factory'
,
'js/api_admin/catalog_preview_factory'
,
'js/api_admin/catalog_preview_factory'
,
'js/courseware/courseware_factory'
,
'js/courseware/courseware_factory'
,
'js/discovery/discovery_factory'
,
'js/discovery/discovery_factory'
,
...
...
lms/static/sass/discussion/_build.scss
View file @
89f93df4
...
@@ -27,6 +27,7 @@ $static-path: '../..' !default;
...
@@ -27,6 +27,7 @@ $static-path: '../..' !default;
// Discussion styling
// Discussion styling
@import
'mixins'
;
@import
'mixins'
;
@import
'discussion'
;
// Process old file after definitions but before everything else, partial is deprecated.
@import
'discussion'
;
// Process old file after definitions but before everything else, partial is deprecated.
@import
'layouts'
;
@import
'elements/actions'
;
@import
'elements/actions'
;
@import
'elements/editor'
;
@import
'elements/editor'
;
@import
'elements/labels'
;
@import
'elements/labels'
;
...
...
lms/static/sass/discussion/_discussion.scss
View file @
89f93df4
...
@@ -4,10 +4,6 @@
...
@@ -4,10 +4,6 @@
body
.discussion
{
body
.discussion
{
.course-tabs
.right
{
@include
float
(
right
);
}
.edit-post-form
{
.edit-post-form
{
@include
clearfix
();
@include
clearfix
();
box-sizing
:
border-box
;
box-sizing
:
border-box
;
...
@@ -65,39 +61,6 @@ body.discussion {
...
@@ -65,39 +61,6 @@ body.discussion {
font-size
:
21px
;
font-size
:
21px
;
}
}
section
.user-profile
{
// TODO: don't use table-cell for layout purposes as it switches the screenreader mode
display
:
table-cell
;
border-right
:
1px
solid
#ddd
;
border-radius
:
3px
0
0
3px
;
background-color
:
$sidebar-color
;
box-shadow
:
none
;
.user-profile
{
padding
:
32px
36px
;
}
.sidebar-username
{
font-weight
:
700
;
font-size
:
18px
;
}
.sidebar-user-roles
{
margin-top
:
6px
;
font-style
:
italic
;
font-size
:
13px
;
}
.sidebar-threads-count
{
margin-top
:
14px
;
}
.sidebar-threads-count
span
,
.sidebar-comments-count
span
{
font-weight
:
700
;
}
}
.wmd-panel
{
.wmd-panel
{
width
:
100%
;
width
:
100%
;
}
}
...
@@ -227,16 +190,6 @@ body.discussion {
...
@@ -227,16 +190,6 @@ body.discussion {
text-align
:
center
;
text-align
:
center
;
}
}
.discussion-column
{
@include
float
(
right
);
box-sizing
:
border-box
;
padding-left
:
(
$baseline
/
2
);
width
:
68%
;
max-width
:
800px
;
min-height
:
500px
;
border-radius
:
3px
;
}
.discussion-article
{
.discussion-article
{
position
:
relative
;
position
:
relative
;
min-height
:
500px
;
min-height
:
500px
;
...
@@ -406,108 +359,6 @@ body.discussion {
...
@@ -406,108 +359,6 @@ body.discussion {
.threads
{
.threads
{
}
}
.discussion-thread
{
padding
:
0
;
margin-bottom
:
$baseline
;
@include
transition
(
all
.25s
linear
0s
);
p
{
margin-bottom
:
0
;
}
.discussion-article
{
@include
transition
(
all
.2s
linear
0s
);
border
:
1px
solid
$forum-color-border
;
border-radius
:
3px
;
min-height
:
0
;
background
:
$forum-color-background
;
box-shadow
:
0
1px
0
$shadow
;
@include
transition
(
all
.2s
linear
0s
);
.thread-wrapper
{
@include
border-radius
(
3px
,
3px
,
0
,
0
);
position
:
relative
;
overflow-x
:
hidden
;
overflow-y
:
auto
;
max-height
:
600px
;
background-color
:
$forum-color-background
;
.discussion-post
{
.inline-comment-count
{
@extend
%ui-depth2
;
position
:
relative
;
float
:
right
;
display
:
block
;
height
:
27px
;
margin-top
:
6px
;
margin-right
:
8px
;
padding
:
0
8px
;
border-radius
:
(
$baseline
/
4
);
font-size
:
12px
;
font-weight
:
400
;
line-height
:
25px
;
color
:
#888
;
}
}
.responses
{
header
{
padding-bottom
:
0
;
margin-bottom
:
(
$baseline
*
0
.75
);
.posted-by
{
float
:
left
;
margin-right
:
(
$baseline
/
4
);
font-size
:
16px
;
}
}
.response-body
{
margin-bottom
:
0
.2em
;
font-size
:
14px
;
}
}
.discussion-reply-new
{
.wmd-input
{
height
:
120px
;
}
}
// Content that is hidden by default in the inline view
.post-extended-content
{
display
:
none
;
}
}
.post-tools
{
box-shadow
:
0
1px
1px
$shadow
inset
;
background
:
$gray-l6
;
&
:hover
{
background
:
#fcfcfc
;
.icon
{
color
:
$link-hover
;
}
}
a
{
display
:
block
;
padding
:
(
$baseline
*
0
.25
)
$baseline
;
font-size
:
12px
;
line-height
:
30px
;
.icon
{
color
:
$link-color
;
margin-right
:
(
$baseline
*
0
.25
);
}
}
}
}
}
}
}
.new-post-article
{
.new-post-article
{
...
@@ -603,14 +454,6 @@ body.discussion {
...
@@ -603,14 +454,6 @@ body.discussion {
}
}
}
}
.discussion-user-threads
{
@extend
.discussion-module
;
.discussion-post
{
padding-bottom
:
$baseline
!
important
;
}
}
.xblock-student_view-discussion
{
.xblock-student_view-discussion
{
@extend
%ui-print-excluded
;
@extend
%ui-print-excluded
;
}
}
...
...
lms/static/sass/discussion/_layouts.scss
0 → 100644
View file @
89f93df4
// Layouts for discussion pages
section
.user-profile
{
background-color
:
$sidebar-color
;
.user-profile
{
padding
:
32px
36px
;
min-height
:
500px
;
}
.sidebar-username
{
font-weight
:
700
;
font-size
:
18px
;
}
.sidebar-user-roles
{
margin-top
:
6px
;
font-style
:
italic
;
font-size
:
13px
;
}
.sidebar-threads-count
{
margin-top
:
14px
;
}
.sidebar-threads-count
span
,
.sidebar-comments-count
span
{
font-weight
:
700
;
}
}
.discussion-column
{
@include
float
(
right
);
box-sizing
:
border-box
;
padding-left
:
(
$baseline
/
2
);
width
:
68%
;
max-width
:
800px
;
min-height
:
500px
;
border-radius
:
3px
;
}
lms/static/sass/discussion/views/_thread.scss
View file @
89f93df4
...
@@ -9,11 +9,8 @@
...
@@ -9,11 +9,8 @@
// * +post - individual element styling
// * +post - individual element styling
// * +post - answered question - collapsed comment area
// * +post - answered question - collapsed comment area
// +general thread layout
// post layout
body
.discussion
,
.discussion-module
{
.discussion-post
{
// post layout
.discussion-post
{
padding
:
0
(
$baseline
/
2
);
padding
:
0
(
$baseline
/
2
);
.wrapper-post-header
{
.wrapper-post-header
{
...
@@ -33,9 +30,9 @@ body.discussion, .discussion-module {
...
@@ -33,9 +30,9 @@ body.discussion, .discussion-module {
.post-body
{
.post-body
{
width
:
flex-grid
(
10
,
12
);
width
:
flex-grid
(
10
,
12
);
}
}
}
}
.posted-details
{
.posted-details
{
@extend
%t-copy-sub2
;
@extend
%t-copy-sub2
;
margin-top
:
(
$baseline
/
5
);
margin-top
:
(
$baseline
/
5
);
color
:
$gray-d1
;
color
:
$gray-d1
;
...
@@ -48,10 +45,10 @@ body.discussion, .discussion-module {
...
@@ -48,10 +45,10 @@ body.discussion, .discussion-module {
.timeago
,
.top-post-status
{
.timeago
,
.top-post-status
{
color
:
inherit
;
color
:
inherit
;
}
}
}
}
// response layout
// response layout
.discussion-response
{
.discussion-response
{
min-height
:
(
$baseline
*
5
);
min-height
:
(
$baseline
*
5
);
.response-header-content
{
.response-header-content
{
...
@@ -66,10 +63,10 @@ body.discussion, .discussion-module {
...
@@ -66,10 +63,10 @@ body.discussion, .discussion-module {
position
:
absolute
;
position
:
absolute
;
top
:
$baseline
;
top
:
$baseline
;
}
}
}
}
// comments layout
// comments layout
.discussion-comment
{
.discussion-comment
{
.response-body
{
.response-body
{
@extend
%t-copy-sub2
;
@extend
%t-copy-sub2
;
...
@@ -85,7 +82,6 @@ body.discussion, .discussion-module {
...
@@ -85,7 +82,6 @@ body.discussion, .discussion-module {
.comment-actions-list
{
.comment-actions-list
{
@include
float
(
right
);
@include
float
(
right
);
}
}
}
}
}
// +thread - wrapper styling
// +thread - wrapper styling
...
@@ -186,12 +182,6 @@ body.view-in-course .discussion-article {
...
@@ -186,12 +182,6 @@ body.view-in-course .discussion-article {
// Layout control for discussion modules that does not apply to the discussion board
// Layout control for discussion modules that does not apply to the discussion board
.discussion-module
{
.discussion-module
{
.discussion-thread
{
.thread-main-wrapper
,
.thread-responses-wrapper
{
padding
:
$baseline
;
}
}
.btn-brand
{
.btn-brand
{
@include
blue-button
;
@include
blue-button
;
display
:
inline-block
;
display
:
inline-block
;
...
@@ -204,6 +194,120 @@ body.view-in-course .discussion-article {
...
@@ -204,6 +194,120 @@ body.view-in-course .discussion-article {
}
}
}
}
// Styling for discussion threads
.discussion-thread
{
padding
:
0
;
margin-bottom
:
$baseline
;
@include
transition
(
all
.25s
linear
0s
);
p
{
margin-bottom
:
0
;
}
.thread-main-wrapper
,
.thread-responses-wrapper
{
padding
:
$baseline
;
}
.discussion-article
{
@include
transition
(
all
.2s
linear
0s
);
border
:
1px
solid
$forum-color-border
;
border-radius
:
3px
;
min-height
:
0
;
background
:
$forum-color-background
;
box-shadow
:
0
1px
0
$shadow
;
@include
transition
(
all
.2s
linear
0s
);
.thread-wrapper
{
@include
border-radius
(
3px
,
3px
,
0
,
0
);
position
:
relative
;
overflow-x
:
hidden
;
overflow-y
:
auto
;
max-height
:
600px
;
background-color
:
$forum-color-background
;
.discussion-post
{
.inline-comment-count
{
@extend
%ui-depth2
;
position
:
relative
;
float
:
right
;
display
:
block
;
height
:
27px
;
margin-top
:
6px
;
margin-right
:
8px
;
padding
:
0
8px
;
border-radius
:
(
$baseline
/
4
);
font-size
:
12px
;
font-weight
:
400
;
line-height
:
25px
;
color
:
#888
;
}
}
.responses
{
header
{
padding-bottom
:
0
;
margin-bottom
:
(
$baseline
*
0
.75
);
.posted-by
{
float
:
left
;
margin-right
:
(
$baseline
/
4
);
font-size
:
16px
;
}
}
.response-body
{
margin-bottom
:
0
.2em
;
font-size
:
14px
;
}
}
.discussion-reply-new
{
.wmd-input
{
height
:
120px
;
}
}
// Content that is hidden by default in the inline view
.post-extended-content
{
display
:
none
;
}
}
.post-tools
{
box-shadow
:
0
1px
1px
$shadow
inset
;
background
:
$gray-l6
;
&
:hover
{
background
:
#fcfcfc
;
.icon
{
color
:
$link-hover
;
}
}
a
{
display
:
block
;
padding
:
(
$baseline
*
0
.25
)
$baseline
;
font-size
:
12px
;
line-height
:
30px
;
.icon
{
color
:
$link-color
;
margin-right
:
(
$baseline
*
0
.25
);
}
}
}
}
}
// Custom styling for the list of user threads
.discussion-user-threads
{
.discussion-post
{
padding
:
$baseline
/
2
;
}
}
.thread-wrapper
,
.thread-wrapper
,
.forum-new-post-form
{
.forum-new-post-form
{
img
{
img
{
...
...
lms/static/sass/shared-v2/_layouts.scss
View file @
89f93df4
...
@@ -8,6 +8,7 @@
...
@@ -8,6 +8,7 @@
}
}
.container
{
.container
{
@include
clearfix
();
border
:
1px
solid
$lms-border-color
;
border
:
1px
solid
$lms-border-color
;
background-color
:
$lms-container-background-color
;
background-color
:
$lms-container-background-color
;
padding
:
$baseline
;
padding
:
$baseline
;
...
...
lms/urls.py
View file @
89f93df4
...
@@ -726,6 +726,12 @@ if settings.FEATURES.get('ENABLE_DISCUSSION_SERVICE'):
...
@@ -726,6 +726,12 @@ if settings.FEATURES.get('ENABLE_DISCUSSION_SERVICE'):
include
(
'django_comment_client.urls'
)
include
(
'django_comment_client.urls'
)
),
),
url
(
url
(
r'^courses/{}/discussion/forum/'
.
format
(
settings
.
COURSE_ID_PATTERN
,
),
include
(
'discussion.urls'
)
),
url
(
r'^notification_prefs/enable/'
,
r'^notification_prefs/enable/'
,
'notification_prefs.views.ajax_enable'
'notification_prefs.views.ajax_enable'
),
),
...
...
setup.py
View file @
89f93df4
...
@@ -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.
5
"
,
version
=
"0.
6
"
,
install_requires
=
[
"setuptools"
],
install_requires
=
[
"setuptools"
],
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
...
@@ -23,7 +23,7 @@ setup(
...
@@ -23,7 +23,7 @@ setup(
"ccx = lms.djangoapps.ccx.plugins:CcxCourseTab"
,
"ccx = lms.djangoapps.ccx.plugins:CcxCourseTab"
,
"courseware = lms.djangoapps.courseware.tabs:CoursewareTab"
,
"courseware = lms.djangoapps.courseware.tabs:CoursewareTab"
,
"course_info = lms.djangoapps.courseware.tabs:CourseInfoTab"
,
"course_info = lms.djangoapps.courseware.tabs:CourseInfoTab"
,
"discussion = lms.djangoapps.d
jango_comment_client.forum.view
s:DiscussionTab"
,
"discussion = lms.djangoapps.d
iscussion.plugin
s:DiscussionTab"
,
"edxnotes = lms.djangoapps.edxnotes.plugins:EdxNotesTab"
,
"edxnotes = lms.djangoapps.edxnotes.plugins:EdxNotesTab"
,
"external_discussion = lms.djangoapps.courseware.tabs:ExternalDiscussionCourseTab"
,
"external_discussion = lms.djangoapps.courseware.tabs:ExternalDiscussionCourseTab"
,
"external_link = lms.djangoapps.courseware.tabs:ExternalLinkCourseTab"
,
"external_link = lms.djangoapps.courseware.tabs:ExternalLinkCourseTab"
,
...
...
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