Refactor out a new discussions Django app
This commit is contained in:
committed by
Brian Jacobel
parent
c4e5b2019d
commit
89f93df46e
@@ -1037,7 +1037,7 @@ class DiscussionUserProfileTest(UniqueCourseTest):
|
||||
Tests for user profile page in discussion tab.
|
||||
"""
|
||||
|
||||
PAGE_SIZE = 20 # django_comment_client.forum.views.THREADS_PER_PAGE
|
||||
PAGE_SIZE = 20 # discussion.views.THREADS_PER_PAGE
|
||||
PROFILED_USERNAME = "profiled-user"
|
||||
|
||||
def setUp(self):
|
||||
|
||||
@@ -754,7 +754,7 @@ class DiscussionLinkTestCase(TabTestCase):
|
||||
"""Custom reverse function"""
|
||||
def reverse_discussion_link(viewname, args):
|
||||
"""reverse lookup for discussion link"""
|
||||
if viewname == "django_comment_client.forum.views.forum_form_discussion" and args == [unicode(course.id)]:
|
||||
if viewname == "discussion.views.forum_form_discussion" and args == [unicode(course.id)]:
|
||||
return "default_discussion_link"
|
||||
return reverse_discussion_link
|
||||
|
||||
|
||||
28
lms/djangoapps/discussion/plugins.py
Normal file
28
lms/djangoapps/discussion/plugins.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
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)
|
||||
@@ -0,0 +1,36 @@
|
||||
;(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);
|
||||
@@ -0,0 +1,29 @@
|
||||
;(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);
|
||||
@@ -9,25 +9,38 @@
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.template.defaultfilters import escapejs
|
||||
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="pagetitle">${_("Discussion - {course_number}").format(course_number=course.display_number_with_default)}</%block>
|
||||
|
||||
<%block name="headextra">
|
||||
<%include file="_js_head_dependencies.html" />
|
||||
<%include file="../discussion/_js_head_dependencies.html" />
|
||||
</%block>
|
||||
|
||||
<%block name="js_extra">
|
||||
## 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'/>
|
||||
<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>
|
||||
|
||||
<%include file="_discussion_course_navigation.html" args="active_page='discussion'" />
|
||||
<%include file="/discussion/_discussion_course_navigation.html" args="active_page='discussion'" />
|
||||
|
||||
<%block name="content">
|
||||
<section class="discussion container" id="discussion-container"
|
||||
<section class="discussion discussion-board container" id="discussion-container"
|
||||
data-roles="${roles}"
|
||||
data-course-id="${course_id}"
|
||||
data-course-name="${course.display_name_with_default}"
|
||||
@@ -0,0 +1,62 @@
|
||||
## 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" />
|
||||
@@ -10,7 +10,6 @@ from django.utils import translation
|
||||
from lms.lib.comment_client.utils import CommentClientPaginatedResult
|
||||
|
||||
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.tests.group_id import (
|
||||
CohortedTopicGroupIdTestMixin,
|
||||
@@ -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.utils import CohortedTestCase
|
||||
from django_comment_client.utils import strip_none
|
||||
from lms.djangoapps.discussion import views
|
||||
from student.tests.factories import UserFactory, CourseEnrollmentFactory
|
||||
from util.testing import UrlResetMixin
|
||||
from openedx.core.djangoapps.util.testing import ContentGroupTestCase
|
||||
@@ -87,7 +87,7 @@ class ViewsExceptionTestCase(UrlResetMixin, ModuleStoreTestCase):
|
||||
# that gets the current user's info
|
||||
mock_from_django_user.return_value = Mock()
|
||||
|
||||
url = reverse('django_comment_client.forum.views.user_profile',
|
||||
url = reverse('discussion.views.user_profile',
|
||||
kwargs={'course_id': self.course.id.to_deprecated_string(), 'user_id': '12345'}) # There is no user 12345
|
||||
self.response = self.client.get(url)
|
||||
self.assertEqual(self.response.status_code, 404)
|
||||
@@ -104,7 +104,7 @@ class ViewsExceptionTestCase(UrlResetMixin, ModuleStoreTestCase):
|
||||
# that gets the current user's info
|
||||
mock_from_django_user.return_value = Mock()
|
||||
|
||||
url = reverse('django_comment_client.forum.views.followed_threads',
|
||||
url = reverse('discussion.views.followed_threads',
|
||||
kwargs={'course_id': self.course.id.to_deprecated_string(), 'user_id': '12345'}) # There is no user 12345
|
||||
self.response = self.client.get(url)
|
||||
self.assertEqual(self.response.status_code, 404)
|
||||
@@ -1257,7 +1257,7 @@ class CommentsServiceRequestHeadersTestCase(UrlResetMixin, ModuleStoreTestCase):
|
||||
|
||||
self.client.get(
|
||||
reverse(
|
||||
"django_comment_client.forum.views.single_thread",
|
||||
"discussion.views.single_thread",
|
||||
kwargs={
|
||||
"course_id": self.course.id.to_deprecated_string(),
|
||||
"discussion_id": "dummy_discussion_id",
|
||||
@@ -1274,7 +1274,7 @@ class CommentsServiceRequestHeadersTestCase(UrlResetMixin, ModuleStoreTestCase):
|
||||
|
||||
self.client.get(
|
||||
reverse(
|
||||
"django_comment_client.forum.views.forum_form_discussion",
|
||||
"discussion.views.forum_form_discussion",
|
||||
kwargs={"course_id": self.course.id.to_deprecated_string()}
|
||||
),
|
||||
)
|
||||
@@ -1361,7 +1361,7 @@ class ForumDiscussionXSSTestCase(UrlResetMixin, ModuleStoreTestCase):
|
||||
Test that XSS attack is prevented
|
||||
"""
|
||||
reverse_url = "%s%s" % (reverse(
|
||||
"django_comment_client.forum.views.forum_form_discussion",
|
||||
"discussion.views.forum_form_discussion",
|
||||
kwargs={"course_id": unicode(self.course.id)}), '/forum_form_discussion')
|
||||
# Test that malicious code does not appear in html
|
||||
url = "%s?%s=%s" % (reverse_url, 'sort_key', malicious_code)
|
||||
@@ -1380,7 +1380,7 @@ class ForumDiscussionXSSTestCase(UrlResetMixin, ModuleStoreTestCase):
|
||||
mock_from_django_user.return_value = Mock()
|
||||
mock_request.side_effect = make_mock_request_impl(course=self.course, text='dummy')
|
||||
|
||||
url = reverse('django_comment_client.forum.views.user_profile',
|
||||
url = reverse('discussion.views.user_profile',
|
||||
kwargs={'course_id': unicode(self.course.id), 'user_id': str(self.student.id)})
|
||||
# Test that malicious code does not appear in html
|
||||
url_string = "%s?%s=%s" % (url, 'page', malicious_code)
|
||||
@@ -4,7 +4,7 @@ Forum urls for the django_comment_client.
|
||||
from django.conf.urls import url, patterns
|
||||
|
||||
urlpatterns = patterns(
|
||||
'django_comment_client.forum.views',
|
||||
'discussion.views',
|
||||
|
||||
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'),
|
||||
@@ -7,23 +7,20 @@ import json
|
||||
import logging
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.conf import settings
|
||||
from django.core.context_processors import csrf
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.models import User
|
||||
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
|
||||
import newrelic.agent
|
||||
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from courseware.courses import get_course_with_access
|
||||
from openedx.core.djangoapps.course_groups.cohorts import (
|
||||
is_course_cohorted,
|
||||
get_cohort_id,
|
||||
get_course_cohorts,
|
||||
)
|
||||
from courseware.tabs import EnrolledTab
|
||||
from courseware.access import has_access
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
@@ -48,25 +45,6 @@ PAGES_NEARBY_DELTA = 2
|
||||
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()
|
||||
def make_course_settings(course, user):
|
||||
"""
|
||||
@@ -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
|
||||
cc_user = cc.User.from_django_user(request.user)
|
||||
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']
|
||||
else:
|
||||
# If the user clicked a sort key, update their default sort 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]
|
||||
except cc.utils.CommentClientMaintenanceError:
|
||||
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:
|
||||
return HttpResponseBadRequest("Invalid group_id")
|
||||
|
||||
@@ -290,7 +272,7 @@ def forum_form_discussion(request, course_key):
|
||||
'uses_pattern_library': True,
|
||||
}
|
||||
# print "start rendering.."
|
||||
return render_to_response('discussion/index.html', context)
|
||||
return render_to_response('discussion/discussion_board.html', context)
|
||||
|
||||
|
||||
@require_GET
|
||||
@@ -318,8 +300,8 @@ def single_thread(request, course_key, discussion_id, thread_id):
|
||||
response_skip=request.GET.get("resp_skip"),
|
||||
response_limit=request.GET.get("resp_limit")
|
||||
)
|
||||
except cc.utils.CommentClientRequestError as e:
|
||||
if e.status_code == 404:
|
||||
except cc.utils.CommentClientRequestError as error:
|
||||
if error.status_code == 404:
|
||||
raise Http404
|
||||
raise
|
||||
|
||||
@@ -404,7 +386,7 @@ def single_thread(request, course_key, discussion_id, thread_id):
|
||||
'disable_courseware_js': True,
|
||||
'uses_pattern_library': True,
|
||||
}
|
||||
return render_to_response('discussion/index.html', context)
|
||||
return render_to_response('discussion/discussion_board.html', context)
|
||||
|
||||
|
||||
@require_GET
|
||||
@@ -465,7 +447,9 @@ def user_profile(request, course_key, user_id):
|
||||
'annotated_content_info': json.dumps(annotated_content_info),
|
||||
'page': query_params['page'],
|
||||
'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)
|
||||
@@ -6,6 +6,5 @@ from django.conf.urls import url, patterns, include
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
|
||||
url(r'forum/?', include('django_comment_client.forum.urls')),
|
||||
url(r'', include('django_comment_client.base.urls')),
|
||||
)
|
||||
|
||||
@@ -591,10 +591,10 @@ def permalink(content):
|
||||
else:
|
||||
course_id = content['course_id']
|
||||
if content['type'] == 'thread':
|
||||
return reverse('django_comment_client.forum.views.single_thread',
|
||||
return reverse('discussion.views.single_thread',
|
||||
args=[course_id, content['commentable_id'], content['id']])
|
||||
else:
|
||||
return reverse('django_comment_client.forum.views.single_thread',
|
||||
return reverse('discussion.views.single_thread',
|
||||
args=[course_id, content['commentable_id'], content['thread_id']]) + '#' + content['id']
|
||||
|
||||
|
||||
|
||||
@@ -1941,8 +1941,10 @@ INSTALLED_APPS = (
|
||||
'django_comment_client',
|
||||
'django_comment_common',
|
||||
'discussion_api',
|
||||
'notes',
|
||||
'lms.djangoapps.discussion',
|
||||
|
||||
# Notes
|
||||
'notes',
|
||||
'edxnotes',
|
||||
|
||||
# Splash screen
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
* done.
|
||||
*/
|
||||
modules: getModulesList([
|
||||
'discussion/js/discussion_board_factory',
|
||||
'js/api_admin/catalog_preview_factory',
|
||||
'js/courseware/courseware_factory',
|
||||
'js/discovery/discovery_factory',
|
||||
|
||||
@@ -27,6 +27,7 @@ $static-path: '../..' !default;
|
||||
// Discussion styling
|
||||
@import 'mixins';
|
||||
@import 'discussion'; // Process old file after definitions but before everything else, partial is deprecated.
|
||||
@import 'layouts';
|
||||
@import 'elements/actions';
|
||||
@import 'elements/editor';
|
||||
@import 'elements/labels';
|
||||
|
||||
@@ -4,10 +4,6 @@
|
||||
|
||||
body.discussion {
|
||||
|
||||
.course-tabs .right {
|
||||
@include float(right);
|
||||
}
|
||||
|
||||
.edit-post-form {
|
||||
@include clearfix();
|
||||
box-sizing: border-box;
|
||||
@@ -65,39 +61,6 @@ body.discussion {
|
||||
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 {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -227,16 +190,6 @@ body.discussion {
|
||||
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 {
|
||||
position: relative;
|
||||
min-height: 500px;
|
||||
@@ -406,108 +359,6 @@ body.discussion {
|
||||
|
||||
.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 {
|
||||
@@ -603,14 +454,6 @@ body.discussion {
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-user-threads {
|
||||
@extend .discussion-module;
|
||||
|
||||
.discussion-post {
|
||||
padding-bottom: $baseline !important;
|
||||
}
|
||||
}
|
||||
|
||||
.xblock-student_view-discussion {
|
||||
@extend %ui-print-excluded;
|
||||
}
|
||||
|
||||
40
lms/static/sass/discussion/_layouts.scss
Normal file
40
lms/static/sass/discussion/_layouts.scss
Normal file
@@ -0,0 +1,40 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -9,136 +9,132 @@
|
||||
// * +post - individual element styling
|
||||
// * +post - answered question - collapsed comment area
|
||||
|
||||
// +general thread layout
|
||||
body.discussion, .discussion-module {
|
||||
|
||||
// post layout
|
||||
.discussion-post {
|
||||
// post layout
|
||||
.discussion-post {
|
||||
padding: 0 ($baseline/2);
|
||||
|
||||
.wrapper-post-header {
|
||||
padding-bottom: 0;
|
||||
margin-bottom: ($baseline*0.75);
|
||||
padding-bottom: 0;
|
||||
margin-bottom: ($baseline*0.75);
|
||||
}
|
||||
|
||||
.post-header-content {
|
||||
display: inline-block;
|
||||
width: flex-grid(9,12);
|
||||
display: inline-block;
|
||||
width: flex-grid(9,12);
|
||||
}
|
||||
|
||||
.post-header-actions {
|
||||
@include float(right);
|
||||
@include float(right);
|
||||
}
|
||||
|
||||
.post-body {
|
||||
width: flex-grid(10,12);
|
||||
width: flex-grid(10,12);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.posted-details {
|
||||
.posted-details {
|
||||
@extend %t-copy-sub2;
|
||||
margin-top: ($baseline/5);
|
||||
color: $gray-d1;
|
||||
|
||||
.username {
|
||||
@extend %t-strong;
|
||||
display: inline;
|
||||
@extend %t-strong;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.timeago, .top-post-status {
|
||||
color: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// response layout
|
||||
.discussion-response {
|
||||
// response layout
|
||||
.discussion-response {
|
||||
min-height: ($baseline*5);
|
||||
|
||||
.response-header-content {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: flex-grid(11,12);
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: flex-grid(11,12);
|
||||
}
|
||||
|
||||
.response-header-actions {
|
||||
@include float(right);
|
||||
@include right($baseline);
|
||||
position: absolute;
|
||||
top: $baseline;
|
||||
@include float(right);
|
||||
@include right($baseline);
|
||||
position: absolute;
|
||||
top: $baseline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// comments layout
|
||||
.discussion-comment {
|
||||
// comments layout
|
||||
.discussion-comment {
|
||||
|
||||
.response-body {
|
||||
@extend %t-copy-sub2;
|
||||
display: inline-block;
|
||||
margin-bottom: ($baseline/2);
|
||||
width: flex-grid(10,12);
|
||||
@extend %t-copy-sub2;
|
||||
display: inline-block;
|
||||
margin-bottom: ($baseline/2);
|
||||
width: flex-grid(10,12);
|
||||
|
||||
p + p {
|
||||
margin-top: 12px;
|
||||
}
|
||||
p + p {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-actions-list {
|
||||
@include float(right);
|
||||
@include float(right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// +thread - wrapper styling
|
||||
.thread-wrapper {
|
||||
.thread-main-wrapper {
|
||||
padding-bottom: $baseline;
|
||||
}
|
||||
.thread-main-wrapper {
|
||||
padding-bottom: $baseline;
|
||||
}
|
||||
}
|
||||
|
||||
// +thread - elements - shared styles
|
||||
body.discussion {
|
||||
|
||||
.discussion-post, .discussion-response, .discussion-comment {
|
||||
@include clearfix();
|
||||
.discussion-post, .discussion-response, .discussion-comment {
|
||||
@include clearfix();
|
||||
|
||||
// thread - images
|
||||
.author-image {
|
||||
@include margin-right($baseline/2);
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
// thread - images
|
||||
.author-image {
|
||||
@include margin-right($baseline/2);
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
|
||||
// STATE: No profile image
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
// STATE: No profile image
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// CASE: post image
|
||||
&.level-post {
|
||||
height: $post-image-dimension;
|
||||
width: $post-image-dimension;
|
||||
}
|
||||
// CASE: post image
|
||||
&.level-post {
|
||||
height: $post-image-dimension;
|
||||
width: $post-image-dimension;
|
||||
}
|
||||
|
||||
// CASE: response image
|
||||
&.level-response {
|
||||
height: $response-image-dimension;
|
||||
width: $response-image-dimension;
|
||||
}
|
||||
// CASE: response image
|
||||
&.level-response {
|
||||
height: $response-image-dimension;
|
||||
width: $response-image-dimension;
|
||||
}
|
||||
|
||||
// CASE: comment image
|
||||
&.level-comment {
|
||||
height: $comment-image-dimension;
|
||||
width: $comment-image-dimension;
|
||||
}
|
||||
// CASE: comment image
|
||||
&.level-comment {
|
||||
height: $comment-image-dimension;
|
||||
width: $comment-image-dimension;
|
||||
}
|
||||
|
||||
img {
|
||||
border-radius: 3px;
|
||||
}
|
||||
img {
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-response .response-body {
|
||||
@include padding-right($baseline); //ensures content doesn't overlap on post or response actions.
|
||||
}
|
||||
.discussion-response .response-body {
|
||||
@include padding-right($baseline); //ensures content doesn't overlap on post or response actions.
|
||||
}
|
||||
}
|
||||
|
||||
// +post - individual element styling
|
||||
@@ -146,62 +142,170 @@ body.discussion .discussion-post,
|
||||
body.discussion .discussion-article,
|
||||
body.view-in-course .discussion-post,
|
||||
body.view-in-course .discussion-article {
|
||||
// NOTE: discussion-article is used for inline discussion modules.
|
||||
@include clearfix();
|
||||
// NOTE: discussion-article is used for inline discussion modules.
|
||||
@include clearfix();
|
||||
|
||||
.post-header-content {
|
||||
.post-header-content {
|
||||
|
||||
// post title
|
||||
.post-title {
|
||||
@extend %t-title4;
|
||||
@extend %t-ultrastrong;
|
||||
margin-bottom: ($baseline/4);
|
||||
letter-spacing: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// post body
|
||||
.post-body {
|
||||
@extend %t-copy-sub1;
|
||||
// clear: both; //TO-DO: confirm that removing this is ok for all cases of discussion posts.
|
||||
}
|
||||
|
||||
// post context
|
||||
.post-context {
|
||||
@extend %t-copy-sub2;
|
||||
margin-top: $baseline;
|
||||
color: $gray-d1;
|
||||
|
||||
// CASE: no courseware context or cohort visibility rules
|
||||
&:empty {
|
||||
display: none;
|
||||
// post title
|
||||
.post-title {
|
||||
@extend %t-title4;
|
||||
@extend %t-ultrastrong;
|
||||
margin-bottom: ($baseline/4);
|
||||
letter-spacing: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// post visibility - cohorts
|
||||
.group-visibility-label {
|
||||
margin-top: ($baseline/4);
|
||||
// post body
|
||||
.post-body {
|
||||
@extend %t-copy-sub1;
|
||||
// clear: both; //TO-DO: confirm that removing this is ok for all cases of discussion posts.
|
||||
}
|
||||
|
||||
// post context
|
||||
.post-context {
|
||||
@extend %t-copy-sub2;
|
||||
margin-top: $baseline;
|
||||
color: $gray-d1;
|
||||
|
||||
// CASE: no courseware context or cohort visibility rules
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// post visibility - cohorts
|
||||
.group-visibility-label {
|
||||
margin-top: ($baseline/4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Layout control for discussion modules that does not apply to the discussion board
|
||||
.discussion-module {
|
||||
.discussion-thread {
|
||||
.btn-brand {
|
||||
@include blue-button;
|
||||
display: inline-block;
|
||||
padding-bottom: ($baseline/10);
|
||||
height: 37px;
|
||||
|
||||
&:hover, &:focus {
|
||||
border-color: #222;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
padding: $baseline;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-brand {
|
||||
@include blue-button;
|
||||
display: inline-block;
|
||||
padding-bottom: ($baseline/10);
|
||||
height: 37px;
|
||||
.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);
|
||||
|
||||
&:hover, &:focus {
|
||||
border-color: #222;
|
||||
.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,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
}
|
||||
|
||||
.container {
|
||||
@include clearfix();
|
||||
border: 1px solid $lms-border-color;
|
||||
background-color: $lms-container-background-color;
|
||||
padding: $baseline;
|
||||
|
||||
@@ -725,6 +725,12 @@ if settings.FEATURES.get('ENABLE_DISCUSSION_SERVICE'):
|
||||
),
|
||||
include('django_comment_client.urls')
|
||||
),
|
||||
url(
|
||||
r'^courses/{}/discussion/forum/'.format(
|
||||
settings.COURSE_ID_PATTERN,
|
||||
),
|
||||
include('discussion.urls')
|
||||
),
|
||||
url(
|
||||
r'^notification_prefs/enable/',
|
||||
'notification_prefs.views.ajax_enable'
|
||||
|
||||
4
setup.py
4
setup.py
@@ -6,7 +6,7 @@ from setuptools import setup
|
||||
|
||||
setup(
|
||||
name="Open edX",
|
||||
version="0.5",
|
||||
version="0.6",
|
||||
install_requires=["setuptools"],
|
||||
requires=[],
|
||||
# NOTE: These are not the names we should be installing. This tree should
|
||||
@@ -23,7 +23,7 @@ setup(
|
||||
"ccx = lms.djangoapps.ccx.plugins:CcxCourseTab",
|
||||
"courseware = lms.djangoapps.courseware.tabs:CoursewareTab",
|
||||
"course_info = lms.djangoapps.courseware.tabs:CourseInfoTab",
|
||||
"discussion = lms.djangoapps.django_comment_client.forum.views:DiscussionTab",
|
||||
"discussion = lms.djangoapps.discussion.plugins:DiscussionTab",
|
||||
"edxnotes = lms.djangoapps.edxnotes.plugins:EdxNotesTab",
|
||||
"external_discussion = lms.djangoapps.courseware.tabs:ExternalDiscussionCourseTab",
|
||||
"external_link = lms.djangoapps.courseware.tabs:ExternalLinkCourseTab",
|
||||
|
||||
Reference in New Issue
Block a user