Commit 89f93df4 by Andy Armstrong Committed by Brian Jacobel

Refactor out a new discussions Django app

parent c4e5b201
......@@ -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
......
"""
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)
;(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);
;(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}"
......
## 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;
}
......
// 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;
}
......@@ -8,6 +8,7 @@
}
.container {
@include clearfix();
border: 1px solid $lms-border-color;
background-color: $lms-container-background-color;
padding: $baseline;
......
......@@ -726,6 +726,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'
),
......
......@@ -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",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment