From 89f93df46e6227058a4c7ef5c00cdb4a456e4583 Mon Sep 17 00:00:00 2001 From: Andy Armstrong Date: Fri, 1 Jul 2016 17:26:53 -0400 Subject: [PATCH] Refactor out a new discussions Django app --- .../tests/discussion/test_discussion.py | 2 +- lms/djangoapps/courseware/tests/test_tabs.py | 2 +- .../forum => discussion}/__init__.py | 0 lms/djangoapps/discussion/plugins.py | 28 ++ .../discussion/js/discussion_board_factory.js | 36 ++ .../js/discussion_profile_page_factory.js | 29 ++ .../discussion/js}/discussion_router.js | 0 .../discussion_user_profile_view_spec.js | 0 .../js}/views/discussion_user_profile_view.js | 0 .../discussion/discussion_board.html} | 21 +- .../discussion/discussion_profile_page.html} | 0 .../templates/discussion/maintenance.html | 0 .../templates/discussion/user_profile.html | 62 ++++ .../tests/test_views.py} | 14 +- .../forum => discussion}/urls.py | 2 +- .../forum => discussion}/views.py | 44 +-- lms/djangoapps/django_comment_client/urls.py | 1 - lms/djangoapps/django_comment_client/utils.py | 4 +- lms/envs/common.py | 4 +- lms/static/lms/js/build.js | 1 + lms/static/sass/discussion/_build.scss | 1 + lms/static/sass/discussion/_discussion.scss | 157 -------- lms/static/sass/discussion/_layouts.scss | 40 +++ lms/static/sass/discussion/views/_thread.scss | 336 ++++++++++++------ lms/static/sass/shared-v2/_layouts.scss | 1 + lms/urls.py | 6 + setup.py | 4 +- 27 files changed, 472 insertions(+), 323 deletions(-) rename lms/djangoapps/{django_comment_client/forum => discussion}/__init__.py (100%) create mode 100644 lms/djangoapps/discussion/plugins.py create mode 100644 lms/djangoapps/discussion/static/discussion/js/discussion_board_factory.js create mode 100644 lms/djangoapps/discussion/static/discussion/js/discussion_profile_page_factory.js rename {common/static/common/js/discussion => lms/djangoapps/discussion/static/discussion/js}/discussion_router.js (100%) rename {common/static/common/js/spec/discussion/view => lms/djangoapps/discussion/static/discussion/js/spec/views}/discussion_user_profile_view_spec.js (100%) rename {common/static/common/js/discussion => lms/djangoapps/discussion/static/discussion/js}/views/discussion_user_profile_view.js (100%) rename lms/{templates/discussion/index.html => djangoapps/discussion/templates/discussion/discussion_board.html} (69%) rename lms/{templates/discussion/user_profile.html => djangoapps/discussion/templates/discussion/discussion_profile_page.html} (100%) rename lms/{ => djangoapps/discussion}/templates/discussion/maintenance.html (100%) create mode 100644 lms/djangoapps/discussion/templates/discussion/user_profile.html rename lms/djangoapps/{django_comment_client/forum/tests.py => discussion/tests/test_views.py} (99%) rename lms/djangoapps/{django_comment_client/forum => discussion}/urls.py (93%) rename lms/djangoapps/{django_comment_client/forum => discussion}/views.py (95%) create mode 100644 lms/static/sass/discussion/_layouts.scss diff --git a/common/test/acceptance/tests/discussion/test_discussion.py b/common/test/acceptance/tests/discussion/test_discussion.py index 9f200ecd8f..0bc146166d 100644 --- a/common/test/acceptance/tests/discussion/test_discussion.py +++ b/common/test/acceptance/tests/discussion/test_discussion.py @@ -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): diff --git a/lms/djangoapps/courseware/tests/test_tabs.py b/lms/djangoapps/courseware/tests/test_tabs.py index 692b5311fa..2f814bb746 100644 --- a/lms/djangoapps/courseware/tests/test_tabs.py +++ b/lms/djangoapps/courseware/tests/test_tabs.py @@ -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 diff --git a/lms/djangoapps/django_comment_client/forum/__init__.py b/lms/djangoapps/discussion/__init__.py similarity index 100% rename from lms/djangoapps/django_comment_client/forum/__init__.py rename to lms/djangoapps/discussion/__init__.py diff --git a/lms/djangoapps/discussion/plugins.py b/lms/djangoapps/discussion/plugins.py new file mode 100644 index 0000000000..50e5d830b5 --- /dev/null +++ b/lms/djangoapps/discussion/plugins.py @@ -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) diff --git a/lms/djangoapps/discussion/static/discussion/js/discussion_board_factory.js b/lms/djangoapps/discussion/static/discussion/js/discussion_board_factory.js new file mode 100644 index 0000000000..66e45b005a --- /dev/null +++ b/lms/djangoapps/discussion/static/discussion/js/discussion_board_factory.js @@ -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); diff --git a/lms/djangoapps/discussion/static/discussion/js/discussion_profile_page_factory.js b/lms/djangoapps/discussion/static/discussion/js/discussion_profile_page_factory.js new file mode 100644 index 0000000000..79e3ad4b85 --- /dev/null +++ b/lms/djangoapps/discussion/static/discussion/js/discussion_profile_page_factory.js @@ -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); diff --git a/common/static/common/js/discussion/discussion_router.js b/lms/djangoapps/discussion/static/discussion/js/discussion_router.js similarity index 100% rename from common/static/common/js/discussion/discussion_router.js rename to lms/djangoapps/discussion/static/discussion/js/discussion_router.js diff --git a/common/static/common/js/spec/discussion/view/discussion_user_profile_view_spec.js b/lms/djangoapps/discussion/static/discussion/js/spec/views/discussion_user_profile_view_spec.js similarity index 100% rename from common/static/common/js/spec/discussion/view/discussion_user_profile_view_spec.js rename to lms/djangoapps/discussion/static/discussion/js/spec/views/discussion_user_profile_view_spec.js diff --git a/common/static/common/js/discussion/views/discussion_user_profile_view.js b/lms/djangoapps/discussion/static/discussion/js/views/discussion_user_profile_view.js similarity index 100% rename from common/static/common/js/discussion/views/discussion_user_profile_view.js rename to lms/djangoapps/discussion/static/discussion/js/views/discussion_user_profile_view.js diff --git a/lms/templates/discussion/index.html b/lms/djangoapps/discussion/templates/discussion/discussion_board.html similarity index 69% rename from lms/templates/discussion/index.html rename to lms/djangoapps/discussion/templates/discussion/discussion_board.html index 6e0ca8e363..9d44984873 100644 --- a/lms/templates/discussion/index.html +++ b/lms/djangoapps/discussion/templates/discussion/discussion_board.html @@ -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 name="pagetitle">${_("Discussion - {course_number}").format(course_number=course.display_number_with_default)} <%block name="headextra"> -<%include file="_js_head_dependencies.html" /> +<%include file="../discussion/_js_head_dependencies.html" /> <%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'/> + + +<%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") + }); + -<%include file="_discussion_course_navigation.html" args="active_page='discussion'" /> +<%include file="/discussion/_discussion_course_navigation.html" args="active_page='discussion'" /> <%block name="content"> -
+ +<%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 name="pagetitle">${_("Discussion - {course_number}").format(course_number=course.display_number_with_default)} + +<%block name="headextra"> +<%include file="_js_head_dependencies.html" /> + + +<%block name="js_extra"> +<%include file="_js_body_dependencies.html" /> +<%static:js group='discussion'/> + + +<%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") + }); + + + +<%include file="../courseware/course_navigation.html" args="active_page='discussion'" /> + +
+
+ +
+
+
+
+
+
+
+ +<%include file="_underscore_templates.html" /> diff --git a/lms/djangoapps/django_comment_client/forum/tests.py b/lms/djangoapps/discussion/tests/test_views.py similarity index 99% rename from lms/djangoapps/django_comment_client/forum/tests.py rename to lms/djangoapps/discussion/tests/test_views.py index de82c41f0b..69a76bdc90 100644 --- a/lms/djangoapps/django_comment_client/forum/tests.py +++ b/lms/djangoapps/discussion/tests/test_views.py @@ -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) diff --git a/lms/djangoapps/django_comment_client/forum/urls.py b/lms/djangoapps/discussion/urls.py similarity index 93% rename from lms/djangoapps/django_comment_client/forum/urls.py rename to lms/djangoapps/discussion/urls.py index 7064858ed5..4c7939866c 100644 --- a/lms/djangoapps/django_comment_client/forum/urls.py +++ b/lms/djangoapps/discussion/urls.py @@ -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\w+)/followed$', 'followed_threads', name='followed_threads'), url(r'users/(?P\w+)$', 'user_profile', name='user_profile'), diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/discussion/views.py similarity index 95% rename from lms/djangoapps/django_comment_client/forum/views.py rename to lms/djangoapps/discussion/views.py index 2ba8f558a9..2de4d10541 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/discussion/views.py @@ -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) diff --git a/lms/djangoapps/django_comment_client/urls.py b/lms/djangoapps/django_comment_client/urls.py index 03e18b38c2..d2972b4c79 100644 --- a/lms/djangoapps/django_comment_client/urls.py +++ b/lms/djangoapps/django_comment_client/urls.py @@ -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')), ) diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py index e713ce05e3..43172f8e58 100644 --- a/lms/djangoapps/django_comment_client/utils.py +++ b/lms/djangoapps/django_comment_client/utils.py @@ -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'] diff --git a/lms/envs/common.py b/lms/envs/common.py index 7d801363b0..3b380aa83c 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1941,8 +1941,10 @@ INSTALLED_APPS = ( 'django_comment_client', 'django_comment_common', 'discussion_api', - 'notes', + 'lms.djangoapps.discussion', + # Notes + 'notes', 'edxnotes', # Splash screen diff --git a/lms/static/lms/js/build.js b/lms/static/lms/js/build.js index c774837178..0da320d920 100644 --- a/lms/static/lms/js/build.js +++ b/lms/static/lms/js/build.js @@ -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', diff --git a/lms/static/sass/discussion/_build.scss b/lms/static/sass/discussion/_build.scss index f04d7faf40..288f4ff0c7 100644 --- a/lms/static/sass/discussion/_build.scss +++ b/lms/static/sass/discussion/_build.scss @@ -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'; diff --git a/lms/static/sass/discussion/_discussion.scss b/lms/static/sass/discussion/_discussion.scss index 67a0e99127..88b01dd076 100644 --- a/lms/static/sass/discussion/_discussion.scss +++ b/lms/static/sass/discussion/_discussion.scss @@ -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; } diff --git a/lms/static/sass/discussion/_layouts.scss b/lms/static/sass/discussion/_layouts.scss new file mode 100644 index 0000000000..016ee2c442 --- /dev/null +++ b/lms/static/sass/discussion/_layouts.scss @@ -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; +} diff --git a/lms/static/sass/discussion/views/_thread.scss b/lms/static/sass/discussion/views/_thread.scss index c552894829..8ab2ffc6fb 100644 --- a/lms/static/sass/discussion/views/_thread.scss +++ b/lms/static/sass/discussion/views/_thread.scss @@ -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, diff --git a/lms/static/sass/shared-v2/_layouts.scss b/lms/static/sass/shared-v2/_layouts.scss index a54cdd8e73..66b30fb46f 100644 --- a/lms/static/sass/shared-v2/_layouts.scss +++ b/lms/static/sass/shared-v2/_layouts.scss @@ -8,6 +8,7 @@ } .container { + @include clearfix(); border: 1px solid $lms-border-color; background-color: $lms-container-background-color; padding: $baseline; diff --git a/lms/urls.py b/lms/urls.py index 43f49df378..44b1f4147b 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -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' diff --git a/setup.py b/setup.py index b9e2426ea2..08f2637360 100644 --- a/setup.py +++ b/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",